In this document we present the joint analysis of the PASS1A metabolomics datasets.

Load all datasets

Load the data from the cloud, including: phenotypic data, metabolomic datasets, and metabolomics dictionary.

source("~/Desktop/repos/motrpac-bic-norm-qc/tools/supervised_normalization_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/unsupervised_normalization_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/gcp_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/association_analysis_methods.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/data_aux_functions.R")
source("~/Desktop/repos/motrpac/tools/prediction_ml_tools.R")
library(randomForest) # for classification tests
# Load the dmaqc data
merged_dmaqc_data =  load_from_bucket("merged_dmaqc_data2019-10-15.RData",
    "gs://bic_data_analysis/pass1a/pheno_dmaqc/",F)
merged_dmaqc_data = merged_dmaqc_data[[1]]
rownames(merged_dmaqc_data) = as.character(merged_dmaqc_data$vial_label)
# define the tissue variable
merged_dmaqc_data$tissue = merged_dmaqc_data$sampletypedescription
# define the time to freeze variable
merged_dmaqc_data$time_to_freeze = merged_dmaqc_data$calculated.variables.time_death_to_collect_min + 
  merged_dmaqc_data$calculated.variables.time_collect_to_freeze_min
# col time vs. control
# df = data.frame(
#   bid = merged_dmaqc_data$bid,
#   edta_col_time = merged_dmaqc_data$calculated.variables.edta_coll_time,
#   time_to_freeze = merged_dmaqc_data$time_to_freeze,
#   is_control = merged_dmaqc_data$animal.key.is_control,
#   tp = merged_dmaqc_data$animal.key.timepoint,
#   tissue = merged_dmaqc_data$specimen.processing.sampletypedescription
# )
# df = unique(df)
# boxplot(edta_col_time/3600 ~ is_control,df)
# boxplot(edta_col_time/3600 - tp ~ is_control,df)
# wilcox.test(edta_col_time/3600 ~ is_control,df)
# blood freeze times
blood_samples = 
  merged_dmaqc_data$specimen.processing.sampletypedescription ==
  "EDTA Plasma"
blood_freeze_time = 
  as.difftime(merged_dmaqc_data$specimen.processing.t_freeze,units = "mins") -
  as.difftime(merged_dmaqc_data$specimen.processing.t_edtaspin,units="mins")
blood_freeze_time = as.numeric(blood_freeze_time)
time_to_freeze = merged_dmaqc_data$time_to_freeze[blood_samples] = 
  blood_freeze_time[blood_samples]
# Load our parsed metabolomics datasets
metabolomics_parsed_datasets = load_from_bucket(
  file = "metabolomics_parsed_datasets_pass1a_external1.RData",
  bucket = "gs://bic_data_analysis/pass1a/metabolomics/")[[1]]
# # # Read the dictionary
# dict_bucket =
#   "gs://motrpac-external-release1-results/metabolomics_targeted/motrpac_metabolomics_data_dictionary-v1.1.5.txt"
# dict_download = get_single_file_from_bucket_to_local_dir(dict_bucket)
# metabolomics_dict = fread(dict_download[[1]],data.table = F)
# metabolomics_dict[grepl("gluc",metabolomics_dict[,2],ignore.case = T),]

Define the variables to be adjusted for:

biospec_cols = c(
  "acute.test.distance",
  "calculated.variables.time_to_freeze",
  # "calculated.variables.edta_coll_time", # no need - see code above for blood
  "bid" # required for matching datasets
  )
differential_analysis_cols = c(
  "animal.registration.sex",
  "animal.key.timepoint",
  "animal.key.is_control"
)
pipeline_qc_cols = c("sample_order")

Log-transform: effect on variance

Some sites do not use the log transformation on their dataset. In this section we plot the coefficient of variation as a function of the mean instensity. We take a single dataset as an example to show how log-transformed data have reduced dependency and smoother plots.

As an additional analysis we also plot the number of missing values per metabolite as a function of its mean intensity. We show that while there is high correlation some missing values appear in fairely high intensities. This is important for imputation as some sites use some fixed low value instead of knn imputation.

# Plot cv vs means
library(gplots)
d = metabolomics_parsed_datasets[["white_adipose_powder,metab_u_hilicpos,unnamed"]]
dx = d$sample_data
CoV<-function(x){return(sd(x,na.rm = T)/mean(x,na.rm=T))}
dmeans = apply(dx,1,mean,na.rm=T)
CoVs = apply(dx,1,CoV)
inds = !is.na(CoVs)
df = data.frame(Mean_intensity = dmeans[inds],CoV = CoVs[inds])
plot(CoV~Mean_intensity,df,cex=0.5,pch=20,main="Raw data")
lines(lowess(CoV~Mean_intensity,df),lty=2,lwd=2,col="blue")

# Repeat after log2
dx = log(1+d$sample_data,base=2)
dmeans = apply(dx,1,mean,na.rm=T)
CoVs = apply(dx,1,CoV)
inds = !is.na(CoVs)
df = data.frame(Mean_intensity = dmeans[inds],CoV = CoVs[inds])
plot(CoV~Mean_intensity,df,cex=0.5,pch=20,main="Log2 data")
lines(lowess(CoV~Mean_intensity,df),lty=2,lwd=2,col="blue")

# Plot number of NAs vs intensity mean
dx = log(1+d$sample_data,base=2)
dmeans = apply(dx,1,mean,na.rm=T)
num_nas = rowSums(is.na(dx))
df = data.frame(Num_NAs = num_nas[inds],Mean_intensity = dmeans[inds])
rho = cor(df$Num_NAs,df$Mean_intensity,method="spearman")
rho = format(rho,digits=2)
plot(Num_NAs~Mean_intensity,df,cex=0.5,pch=20,
     main=paste("Spearman:",rho))

Data filtering and normalization

We go over each dataset and merge the named and unnamed subparts of the untargeted datasets. For targeted data, we merge datasets that are from the same site and tissue.

Then the preprocessing of the new merged datasets is as follows: (1) log the data (values are raw intensities), (2) remove rows with \(>20\%\) missing values, and (3) impute missing values using knn (k=10).

Important: datasets that do not have unique metabolite names or sample ids probably had errors while parsing and are therefore ignored. Also, untargeted with failed mergning of the unnamed and named subsets (e.g., due to different sample ids) are ignored as well.

Finally we add an additional version for each dataset by directly regressing out the sample order component. See https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4757603/ for more details (trend correction is also called background correction, and this is done for each batch separately).

metabolomics_processed_datasets = c()
# set a binary vector indicating with entries to skip
metabolomics_raw_datasets_skip = rep(F,length(metabolomics_parsed_datasets))
names(metabolomics_raw_datasets_skip) = names(metabolomics_parsed_datasets)
untargeted_merge_errors = list()
for(currname in names(metabolomics_parsed_datasets)){
  
  if(metabolomics_raw_datasets_skip[currname]){next}
  
  arr = strsplit(currname,split=",")[[1]]
  arr = arr[-length(arr)]
  name1 = paste(paste(arr,collapse=","),"named",sep=",")
  name2 = paste(paste(arr,collapse=","),"unnamed",sep=",")
  
  # If dataset is untargeted, merge named and unnamed, update the dataset
  # currname. Skip the dataset if the merge fails, but store and print the errror.
  # If the dataset is targeted - go over all datasets with the same vial labels and 
  # merge if possible. 
  if(name2 %in% names(metabolomics_parsed_datasets)){
    d1 = metabolomics_parsed_datasets[[name1]]
    d2 = metabolomics_parsed_datasets[[name2]]
    newd = NULL
    tryCatch({
      newd = merge_named_and_unnamed_metabolomics_datasets(d1,d2,strict = F)
      }, error = function(e) {}) # can add error handling here
    currname = paste(paste(arr,collapse=","),"untargeted",sep=",")
    metabolomics_raw_datasets_skip[name1] = T
    metabolomics_raw_datasets_skip[name2] = T
    if(is.null(newd)){
      print(paste("# Skipping dataset",
                  currname,
                  "because merging the named and unnamed subsets failed"))
      next
    }
  }
  else{
    metabolomics_raw_datasets_skip[currname] = T
    newd = metabolomics_parsed_datasets[[currname]]
    curr_vials = colnames(newd$sample_data)
    curr_site = tolower(unique(newd$sample_meta$site))
    for(another_dataset in names(metabolomics_raw_datasets_skip)){
      if(metabolomics_raw_datasets_skip[another_dataset]){next}
      another_d = metabolomics_parsed_datasets[[another_dataset]]
      another_vials = colnames(another_d$sample_data)
      another_d_site = tolower(unique(another_d$sample_meta$site))
      another_shared_vials = intersect(another_vials,curr_vials)
      if(curr_site == another_d_site &&
        length(another_shared_vials)==length(another_vials) &&
        length(another_shared_vials)==length(curr_vials)
      ){
          # examine the metadata
          curr_meta = newd$sample_meta
          another_meta = another_d$sample_meta
          # at this point we know that both datasets have the
          # same samples, but not necessarily the same controls
          # these has been partially submitted and we therefore need to
          # intersect here
          shared_meta_samples = intersect(rownames(curr_meta),rownames(another_meta))
          # make sure that the vials, types, and order are the same
          if(length(shared_meta_samples) < 1 ||
            !all(curr_meta[shared_meta_samples,1:3]==
                 another_meta[shared_meta_samples,1:3])){
            next # continue if cannot merge
          }
          try({
            newd = merge_named_and_unnamed_metabolomics_datasets(
              newd,another_d,strict = F) # no strict merge thanks to test above
            metabolomics_raw_datasets_skip[another_dataset] = T
          })
      }
    }
    
    # update the name of the dataset
    # we keep track of the number of datasets from the given site, as some
    # merges may fail
    currname = currname = paste(arr[1],curr_site,sep=",")
    num_currname = sum(grepl(currname,names(metabolomics_processed_datasets)))
    currname = paste(currname,num_currname+1,sep=",")
  }
  
  print(paste("#### Analyzing data from:",currname))
  curr_data  = newd$sample_data
  curr_data2 = recast_numeric_data_frame(curr_data)
  curr_data_mat = as.matrix(curr_data2)
  print(paste("no errors in parsing numeric values:",
              all(curr_data==curr_data_mat,na.rm=T)))

  # floor the data at 0
  curr_data_mat[curr_data_mat<0] = 0
  # Keep a logged version of the data
  curr_data_log = log(curr_data_mat+1,base=2)
  
  # organize the metadata
  curr_meta = merged_dmaqc_data[colnames(curr_data),
        union(biospec_cols,differential_analysis_cols)]
  # remove metadata variables with too many NAs
  na_counts = apply(is.na(curr_meta),2,sum)
  curr_meta = curr_meta[,na_counts/nrow(curr_meta) < 0.1]
  
  # Look at the sample order
  curr_order = newd$sample_meta[rownames(curr_meta),"sample_order"]

  # organize the dataset
  curr_data = curr_data[,rownames(curr_meta)]
  curr_data_log = curr_data_log[,rownames(curr_meta)]
  # remove zero variance rows or rows with many NAs
  rows_to_rem = !(apply(curr_data,1,sd,na.rm=T)>0)
  percent_na = rowSums(is.na(curr_data)) / ncol(curr_data)
  print(paste("number of NA cells:",sum(is.na(curr_data))))
  rows_to_rem = rows_to_rem | percent_na > 0.2
  # remove rows with no annotation
  rows_to_rem = rows_to_rem | newd$data_raw_rownames == ""
  rows_to_rem = rows_to_rem | newd$data_raw_rownames == "-"
  rows_to_rem = rows_to_rem | newd$data_raw_rownames == "_"
  rows_to_rem = rows_to_rem | is.na(newd$data_raw_rownames)
  # remove rows with duplicated representation
  row_duplications = names(which(table(newd$data_raw_rownames)>1))
  if(any(newd$data_raw_rownames[!rows_to_rem] %in% row_duplications)){
    rows_to_rem = rows_to_rem | newd$data_raw_rownames %in% row_duplications
    print("# WARNING: dataset has duplicated row names whose data are now ignored")
    print(paste(row_duplications,collapse=","))
  }
  curr_data = curr_data[!rows_to_rem,]
  curr_data_log = curr_data_log[!rows_to_rem,]
  # impute - use capture.output to avoid prints
  capture.output(
    {curr_data_imp = impute::impute.knn(as.matrix(curr_data_log))$data}
    ,file=NULL)
  curr_data_rnames = newd$data_raw_rownames[!rows_to_rem]
  rownames(curr_data_imp) = curr_data_rnames
  
  # Regress out the sample order
  # lm_regress_matrix works on columns
  curr_data_imp2 = t(lm_regress_out_matrix(t(curr_data_imp),curr_order))
  
  # update the data object
  metabolomics_processed_datasets[[currname]] = newd
  metabolomics_processed_datasets[[currname]]$sample_data = curr_data_imp
  metabolomics_processed_datasets[[currname]]$sample_data_nolog_noimp = curr_data
  metabolomics_processed_datasets[[currname]]$data_raw_rownames = curr_data_rnames
  metabolomics_processed_datasets[[currname]]$sample_meta_parsed = curr_meta
  metabolomics_processed_datasets[[currname]]$sample_data_order_adj = curr_data_imp2
}

save_to_bucket(metabolomics_processed_datasets,
               file="metabolomics_processed_datasets10282019.RData",
               bucket = "gs://bic_data_analysis/pass1a/metabolomics/")

Alternatively load the result from the bucket to save time:

metabolomics_processed_datasets = load_from_bucket(
  file="metabolomics_processed_datasets10282019.RData",
  bucket = "gs://bic_data_analysis/pass1a/metabolomics/"
)[[1]]
Copying gs://bic_data_analysis/pass1a/metabolomics/metabolomics_processed_datasets10282019.RData...
/ [0 files][    0.0 B/236.4 MiB]                                                
-
- [0 files][  2.8 MiB/236.4 MiB]                                                
\
|
| [0 files][ 26.3 MiB/236.4 MiB]                                                
/
/ [0 files][ 49.2 MiB/236.4 MiB]                                                
-
\
\ [0 files][ 70.1 MiB/236.4 MiB]                                                
|
/
/ [0 files][ 93.1 MiB/236.4 MiB]                                                
-
- [0 files][112.7 MiB/236.4 MiB]                                                
\
|
| [0 files][135.4 MiB/236.4 MiB]                                                
/
-
- [0 files][157.0 MiB/236.4 MiB]                                                
\
\ [0 files][180.6 MiB/236.4 MiB]                                                
|
/
/ [0 files][206.6 MiB/236.4 MiB]                                                
-
\
\ [0 files][232.4 MiB/236.4 MiB]   23.8 MiB/s                                   
|
| [1 files][236.4 MiB/236.4 MiB]   21.7 MiB/s                                   

Operation completed over 1 objects/236.4 MiB.                                    

Log-transform: effect on differential analysis

Untargeted data are typically log-transformed and analyzed using linear models. On the other hand, concentration data are sometimes analyzed with the same type of models but using the original data. This raises a problem if we wish to compare exact statistics from these data. In this section we perform residual analysis for single metabolites. Our goal is to identify if concentration data behaves “normally” when not log-transformed. The analysis below examines the residuals of the data after fitting linear models for each metabolite, adjusting for freeze time and sex. We then compare the results with and without the log-transformation, counting the number of metabolites with a significant evidence for non-normally distributed residuals.

Trageted vs. Untargeted: single metabolite comparison

Compute statistics for each dataset

Compare overlaps, effect sizes, and correlations within tissues. Compare targeted-untargeted pairs only. For differential analysis we use the same model as in the analysis above.


# helper function to transform a metabolomics matrix
# to that of its motrpac compound ids
extract_metab_data_from_row_annot<-function(x,row_annot_x){
  # get the coloumn that has the row names
  int_sizes = apply(row_annot_x,2,function(x,y)length(intersect(x,y)),y=rownames(x))
  ind = which(int_sizes==max(int_sizes,na.rm = T))[1]
  row_annot_x = row_annot_x[is.element(row_annot_x[,ind],set=rownames(x)),]
  rownames(row_annot_x) = row_annot_x[,ind]
  shared = intersect(rownames(row_annot_x),rownames(x))
  x = x[shared,]
  row_annot_x = row_annot_x[shared,]
  rownames(x) = row_annot_x$motrpac_comp_name
  return(x)
}

single_metabolite_corrs = list()
single_metabolite_de = c()
named2covered_shared_metabolites = list()
for(nn1 in names(metabolomics_processed_datasets)){
  nn1_tissue = strsplit(nn1,split=",")[[1]][1]
  nn1_tissue = gsub("_powder","",nn1_tissue)
  if(grepl("untargeted",nn1)){next}
  single_metabolite_corrs[[nn1]] = list()
  named2covered_shared_metabolites[[nn1]] = NULL
  for(nn2 in names(metabolomics_processed_datasets)){
    if(nn2 == nn1){next}
    if(!grepl("untargeted",nn2)){next}
    nn2_tissue = strsplit(nn2,split=",")[[1]][1]
    nn2_tissue = gsub("_powder","",nn2_tissue)
    nn2_dataset = strsplit(nn2,split=",")[[1]][2]
    if(nn1_tissue!=nn2_tissue){next}
    # get the numeric datasets and their annotation
    x = metabolomics_processed_datasets[[nn1]]$sample_data
    y = metabolomics_processed_datasets[[nn2]]$sample_data
    row_annot_x = metabolomics_processed_datasets[[nn1]]$row_annot
    row_annot_y = metabolomics_processed_datasets[[nn2]]$row_annot
    # transform metabolite names to the motrpac comp name
    x = extract_metab_data_from_row_annot(x,row_annot_x)
    y = extract_metab_data_from_row_annot(y,row_annot_y)
    # align the sample sets
    bid_y = merged_dmaqc_data[colnames(y),"bid"]
    bid_x = merged_dmaqc_data[colnames(x),"bid"]    
    # step 1: merge samples from the same BID
    if(length(unique(bid_x))!=length(bid_x)){
      x = aggregate_repeated_samples(x,bid_x)
    }
    else{
      colnames(x) = bid_x
    }
    if(length(unique(bid_y))!=length(bid_y)){
      y = aggregate_repeated_samples(y,bid_y)
    }else{
      colnames(y) = bid_y
    }
    # step 2: use the shared bio ids
    shared_bids = as.character(intersect(colnames(y),colnames(x)))
    x = as.matrix(x[,shared_bids])
    y = as.matrix(y[,shared_bids])
    # At this point x and y are over the same BIDs, now we add the metadata
    y_meta = unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
    rownames(y_meta) = y_meta$bid
    y_meta = y_meta[shared_bids,]
    
    # get the shared matebolites
    shared_metabolites = intersect(rownames(x),rownames(y))
    shared_metabolites = na.omit(shared_metabolites)
    if(length(shared_metabolites)==0){next}
    named2covered_shared_metabolites[[nn1]] = union(
      named2covered_shared_metabolites[[nn1]],
      shared_metabolites
    )
    
    # Compute the correlation matrices of the shared metabolites
    if(length(shared_metabolites)>1){
          corrs =cor(t(x[shared_metabolites,]),
                t(y[shared_metabolites,]),method = "spearman")
    }
    else{
          corrs = cor(x[shared_metabolites,],
                y[shared_metabolites,],method = "spearman")
    }
    
    # take the covariates (ignore distances)
    curr_cov_cols = intersect(colnames(y_meta),biospec_cols[2])
    curr_covs = data.frame(y_meta[,curr_cov_cols])
    names(curr_covs) = curr_cov_cols
    curr_covs$sex = y_meta$animal.registration.sex # add sex
    
    # differential analysis
    for(tp in unique(y_meta$animal.key.timepoint)){
      resx = t(apply(
        matrix(x[shared_metabolites,],nrow=length(shared_metabolites)),1,
        pass1a_simple_differential_abundance,
        tps = y_meta$animal.key.timepoint,tp=tp,
        is_control = y_meta$animal.key.is_control,
        covs = curr_covs,return_model=F
      ))
      resy = t(apply(
        matrix(y[shared_metabolites,],nrow=length(shared_metabolites)),1,
        pass1a_simple_differential_abundance,
        tps = y_meta$animal.key.timepoint,tp=tp,
        is_control = y_meta$animal.key.is_control,
        covs = curr_covs,return_model=F
      ))
      # Add dataset information, time point, tissue
      # These are important annotations for our summary matrix
      # called single_metabolite_de below
      added_columns = matrix(cbind(
        rep(nn1,length(shared_metabolites)),
        rep(nn2,length(shared_metabolites)),
        shared_metabolites,
        rep(tp,length(shared_metabolites)),
        rep(nn1_tissue,length(shared_metabolites))
      ),nrow=length(shared_metabolites))
      resx = cbind(resx,rep(T,nrow(resx)))
      colnames(resx)[ncol(resx)] = "is_targeted"
      resy = cbind(resy,rep(F,nrow(resy)))
      colnames(resy)[ncol(resy)] = "is_targeted"
      if(nrow(resx)>1){
        resx = cbind(added_columns[,-2],resx)
        resy = cbind(added_columns[,-1],resy)
      }
      else{
        resx = c(added_columns[,-2],resx)
        resy = c(added_columns[,-1],resy)
      }
      single_metabolite_de = rbind(single_metabolite_de,resx)
      single_metabolite_de = rbind(single_metabolite_de,resy)
    }
    
    single_metabolite_corrs[[nn1]][[nn2]] = corrs
  }
}

# Reformat the results for easier comparison later
single_metabolite_de = data.frame(single_metabolite_de)
names(single_metabolite_de) = c(
  "dataset","metabolite","tp","tissue",
  "Est","Std","Tstat","Pvalue","is_targeted")
for(col in names(single_metabolite_de)[-c(1:4)]){
  single_metabolite_de[[col]] = as.numeric(
    as.character(single_metabolite_de[[col]]))
}
for(col in names(single_metabolite_de)[1:4]){
  single_metabolite_de[[col]] = 
    as.character(single_metabolite_de[[col]])
}
# Remove duplications
rownames(single_metabolite_de) = NULL
for(nn in names(single_metabolite_de)){
  ndig = 5
  if(grepl("pval",nn,ignore.case = T)){
    ndig = 10
  }
  if(is.numeric(single_metabolite_de[[nn]])){
    single_metabolite_de[[nn]] = round(single_metabolite_de[[nn]],digits = ndig)
  }
}
single_metabolite_de = unique(single_metabolite_de)

# a helper function for simplifying dataset names
simplify_metab_dataset_name<-function(s){
  s = gsub("metab_u_","",s)
  s = gsub(",untargeted","",s)
  s = gsub(",named","",s)
  return(s)
}
single_metabolite_de[,1] = sapply(single_metabolite_de[,1],
                                  simplify_metab_dataset_name)

We next transform the data above into tables that contain data for each combination of metabolite, time point, and tissue. These are then used for different meta-analyses: (1) a simple random effects analysis, (2) random effects with a binary covariate indicating if a dataset is targeted or untargeted, (3) redo the RE model of (1) with the targeted data only, and (4) redo the RE model of (1) with the untargeted data only.

library(metafor)
meta_analysis_stats = list()
for(tissue in unique(single_metabolite_de$tissue)){
  for(tp in unique(single_metabolite_de$tp)){
    curr_subset = single_metabolite_de[
      single_metabolite_de$tissue==tissue &
        single_metabolite_de$tp==tp,]
    for(metabolite in unique(curr_subset$metabolite)){
      curr_met_data = curr_subset[
        curr_subset$metabolite==metabolite,]
      curr_met_data$var = curr_met_data$Std^2
      re_model1 = NULL;re_model2=NULL
      re_model_tar = NULL;re_model_untar = NULL
      try({re_model1 = rma.uni(curr_met_data$Est,curr_met_data$var)})
      try({re_model2 = rma.mv(curr_met_data$Est,curr_met_data$var,
                         mods=curr_met_data$is_targeted)})
      try({re_model_tar = rma.uni(
        curr_met_data[curr_met_data$is_targeted==1,"Est"],
        curr_met_data[curr_met_data$is_targeted==1,"var"]
      )})
      try({re_model_untar = rma.uni(
        curr_met_data[curr_met_data$is_targeted==0,"Est"],
        curr_met_data[curr_met_data$is_targeted==0,"var"]
      )})
      meta_analysis_stats[[paste(metabolite,tissue,tp,sep=",")]] = 
        list(curr_met_data=curr_met_data,re_model1=re_model1,
            re_model2 = re_model2,re_model_tar=re_model_tar,
            re_model_untar = re_model_untar)
    }
  }
}
Error in rma.uni(curr_met_data$Est, curr_met_data$var) : 
  Fisher scoring algorithm did not converge. See 'help(rma)' for possible remedies.

Comparison results

We now show some plots to summarize the comparison.

Dataset coverage

We first plot the number and percentage of metabolites in the targeted datasets that are measured in at least one untargeted dataset.

library(ggplot2)
dataset2num_metabolites = sapply(metabolomics_processed_datasets, 
                                 function(x)nrow(x$sample_data))
named_dataset_coverage = sapply(named2covered_shared_metabolites,length)
named_dataset_coverage = data.frame(
  name = names(named_dataset_coverage),
  percentage = named_dataset_coverage /
  dataset2num_metabolites[names(named_dataset_coverage)],
  count = named_dataset_coverage,
  total = dataset2num_metabolites[names(named_dataset_coverage)]
)
# add datasets with no coverage
all_targeted_datasets = names(metabolomics_processed_datasets)
all_targeted_datasets = all_targeted_datasets[!grepl("untarg",all_targeted_datasets)]
zero_coverage_datasets = setdiff(all_targeted_datasets,
                                 named_dataset_coverage$name)
zero_coverage_datasets = data.frame(
  name = zero_coverage_datasets,
  percentage = rep(0,length(zero_coverage_datasets)),
  count = rep(0,length(zero_coverage_datasets)),
  total = dataset2num_metabolites[zero_coverage_datasets]
)
named_dataset_coverage = rbind(named_dataset_coverage,
                           zero_coverage_datasets)
named_dataset_coverage = 
  named_dataset_coverage[order(as.character(named_dataset_coverage$name)),]
print(ggplot(named_dataset_coverage, aes(x=name, y=percentage)) + 
  geom_bar(stat = "identity",width=0.2) + coord_flip() +
  geom_text(data=named_dataset_coverage, 
            aes(name, percentage+0.05, label=count), 
            position = position_dodge(width=0.9),
            size=4) + 
  ggtitle("Targeted dataset: coverage by untargeted"))

Spearman correlations

We examine the average absolute correlation between the platforms (within tissues). Whenever two platforms share more than a single metabolite we plot both the average correlation between the same metabolites and between other metabolites. Adding the average correlation between platforms but with different metabolites is important as it gives some perspective to what a significant correlation is. That is, in many cases below, the average correlation may be greater than expected.

# Next examine the Spearman correlations between platforms
mean_abs<-function(x,...){return(mean(abs(x),...))}
sd_abs<-function(x,...){return(sd(abs(x),...))}
extract_diag_vs_non_diag<-function(corrs,func=mean_abs,...){
  if(length(corrs)==1){
    return(c(same=func(corrs,...),other=NA))
  }
  same = func(diag(corrs),...)
  other = func(
    c(corrs[lower.tri(corrs,diag = F)]),...)
  return(c(same=same,other=other))
}
for(tar_dataset in names(single_metabolite_corrs)){
  l = single_metabolite_corrs[[tar_dataset]]
  if(length(l)==0){next}
  corr_info = as.data.frame(t(sapply(l, extract_diag_vs_non_diag)))
  corr_sd = as.data.frame(t(sapply(l, extract_diag_vs_non_diag,func=sd_abs)))
  rownames(corr_info) = sapply(rownames(corr_info),
      function(x)strsplit(x,split=",")[[1]][2])
  rownames(corr_info) = gsub("metab_u_","",rownames(corr_info))
  rownames(corr_sd) = rownames(corr_info)
  corr_info$dataset = rownames(corr_info)
  corr_sd$dataset = corr_info$dataset
  corr_info = melt(corr_info)
  corr_sd = melt(corr_sd)
  corr_info$sd = corr_sd$value
  print(
    ggplot(corr_info, aes(x=dataset, y=value, fill=variable)) +
      geom_bar(position=position_dodge(), stat="identity", colour='black') +
      geom_errorbar(aes(ymin=value-sd, ymax=value+sd),na.rm=T, 
                   width=.2,position=position_dodge(.9)) +
    ggtitle(tar_dataset) + xlab("Untargeted dataset") + ylab("Spearman") +
      labs(fill = "Pair type") + 
      theme(legend.position="top",legend.direction = "horizontal")
  )
}

Meta-analysis of differential effects

Here we go into the single metabolites comparison in greater detail (again, within tissues).

We start with a few examples. Here are the results for lactate in plasma.

lact_res = meta_analysis_stats[
  grepl("lact",names(meta_analysis_stats),ignore.case = T) &
    grepl("plasma",names(meta_analysis_stats),ignore.case = T)
]
lact_res_hours = sapply(names(lact_res),
            function(x)as.numeric(strsplit(x,split=",")[[1]][3]))
lact_res = lact_res[order(lact_res_hours)]
for(lact_example in names(lact_res)[1:6]){
  curr_labels = gsub("plasma,","",
                     meta_analysis_stats[[lact_example]][[1]][,1])
  forest(meta_analysis_stats[[lact_example]]$re_model1,
       slab = curr_labels,
       main = lact_example,xlab = "Log fc",
       col = "blue",cex = 1.1)
}

We can now check the same analysis for liver:

lact_res = meta_analysis_stats[
  grepl("lact",names(meta_analysis_stats),ignore.case = T) &
    grepl("liver",names(meta_analysis_stats),ignore.case = T)
]
lact_res_hours = sapply(names(lact_res),
            function(x)as.numeric(strsplit(x,split=",")[[1]][3]))
lact_res = lact_res[order(lact_res_hours)]
for(lact_example in names(lact_res)[1:6]){
  curr_labels = gsub("liver,","",
                     meta_analysis_stats[[lact_example]][[1]][,1])
  forest(meta_analysis_stats[[lact_example]]$re_model1,
       slab = meta_analysis_stats[[lact_example]][[1]][,1],
       main = lact_example,xlab = "Log fc",
       col = "blue",cex = 1.1)
}

We now move to a systematic comparison of the datasets. We start with a naive comparison, checking if metabolites are consistently being discovered using a simple \(p=0.001\) threshold for significance.

# Naive comparison
thr = 0.001
naive_analysis_tables = lapply(meta_analysis_stats,
    function(x)table(x$curr_met_data[,"Pvalue"]<thr,
                     x$curr_met_data[,"is_targeted"]))
table_with_sig_results = naive_analysis_tables[
  sapply(naive_analysis_tables,nrow)>1
]
percent_with_sig = 
  length(table_with_sig_results)/length(naive_analysis_tables)
sig_results = sapply(table_with_sig_results,
      function(x)paste(x["TRUE",c("0","1")]>0,collapse=","))
sig_results_counts = table(sig_results)
print("Counting the number of metabolites with p<0.001")
[1] "Counting the number of metabolites with p<0.001"
print(paste("Significant in targeted only:",sig_results_counts["FALSE,TRUE"]))
[1] "Significant in targeted only: 66"
print(paste("Significant in untargeted only:",sig_results_counts["TRUE,FALSE"]))
[1] "Significant in untargeted only: 147"
print(paste("Significant in both:",sig_results_counts["TRUE,TRUE"]))
[1] "Significant in both: 90"

As a more rigorous analysis, we use the meta-analyses to obtain useful statistics for comparison.

# Extract some useful statistics per analysis
# P-value for the difference between targeted and untargeted
targeted_diff_p = 
  sapply(meta_analysis_stats,function(x)x$re_model2$pval[2])
# P-values - targeted vs. untargeted
pvals_tar = sapply(meta_analysis_stats,function(x)x$re_model_tar$pval)
pvals_untar = sapply(meta_analysis_stats,function(x)x$re_model_untar$pval)
pvals_untar = unlist(pvals_untar[sapply(pvals_untar,length)>0])
significant_in = rep("None",length(pvals_untar))
significant_in[pvals_tar<0.001] = "Targeted"
significant_in[pvals_untar<0.001] = "Untargeted"
significant_in[pvals_tar<0.001 & pvals_untar<0.001] = "Both"
significant_diff = targeted_diff_p<0.001
df = data.frame(
  targeted = -log10(pvals_tar),
  untargeted = -log10(pvals_untar),
  significant_in = significant_in,
  significant_diff = significant_diff
)
rho = cor(pvals_tar,pvals_untar,method = "spearman")
rhop = cor.test(pvals_tar,pvals_untar,method = "spearman")$p.value
print(
  ggplot(df, aes(x=targeted, y=untargeted,
                 shape=significant_diff, color=significant_in)) +
    geom_point() +
    ggtitle(paste("-log10 p-values, spearman:",format(rho,digits=2),
                  "(p=",format(rhop,digits=3),")"))
)

print("### Summary of differences in RE models ###")
[1] "### Summary of differences in RE models ###"
print("Model is significant at p<0.001:")
[1] "Model is significant at p<0.001:"
print(table(df$significant_in))

      Both       None   Targeted Untargeted 
       219       2082        117        228 
print("Adding is_targeted as a covariate has p<0.001:")
[1] "Adding is_targeted as a covariate has p<0.001:"
print(table(df$significant_diff))

FALSE  TRUE 
 2425   221 
# Betas - targeted vs. untargeted
betas_tar = sapply(meta_analysis_stats,function(x)x$re_model_tar$beta[1,1])
betas_untar = sapply(meta_analysis_stats,function(x)x$re_model_untar$beta[1,1])
betas_untar = unlist(betas_untar[sapply(betas_untar,length)>0])
df = data.frame(
  targeted = betas_tar,
  untargeted = betas_untar,
  significant_in = significant_in,
  significant_diff = significant_diff
)
rho = cor(betas_untar,betas_tar,method = "spearman")
rhop = cor.test(betas_untar,betas_tar,method = "spearman")$p.value
print(
  ggplot(df, aes(x=targeted, y=untargeted,
                 shape=significant_diff, color=significant_in)) +
    geom_point() +
    ggtitle(paste("Effect sizes, spearman:",format(rho,digits=2),
                  "(p=",format(rhop,digits=3),")"))
)

From the plots above we take the most extreme examples and examine their forest plots.

agree_example = names(sample(which(pvals_tar< 1e-10 & pvals_untar < 1e-10 &
                                     targeted_diff_p > 0.1))[1])
simplify_labels_for_forest<-function(s){
  s = gsub(",untargeted","",s)
  tissue = strsplit(s,split=",")[[1]][1]
  s = gsub(paste(tissue,",",sep=""),"",s)
  return(s)
}
forest(meta_analysis_stats[[agree_example]]$re_model1,
  slab = simplify_labels_for_forest(
    meta_analysis_stats[[agree_example]][[1]][,1]),
  main = paste(agree_example,"significant in both, tar and untar agree",sep="\n"),
  xlab = "Log fc",col = "blue")

agree_p_disagree_beta = names(sample(which(pvals_tar< 1e-10 & pvals_untar < 1e-10 &
                                     targeted_diff_p < 0.001))[1])
forest(meta_analysis_stats[[agree_p_disagree_beta]]$re_model1,
  slab = simplify_labels_for_forest(
    meta_analysis_stats[[agree_p_disagree_beta]][[1]][,1]),
  main = paste(agree_p_disagree_beta,
               "significant in both, tar and untar disagree",sep="\n"),
  xlab = "Log fc",col = "blue")

disagree_example1 = names(sample(which(pvals_tar< 1e-20 & pvals_untar >0.1))[1])
forest(meta_analysis_stats[[disagree_example1]]$re_model1,
  slab = simplify_labels_for_forest(
    meta_analysis_stats[[disagree_example1]][[1]][,1]),
  main = paste(disagree_example1,
               "significant targeted, tar and untar disagree",sep="\n"),
  xlab = "Log fc",col = "blue")

disagree_example2 = names(sample(which(pvals_tar > 0.1 & pvals_untar < 1e-20))[1])
forest(meta_analysis_stats[[disagree_example2]]$re_model1,
  slab = simplify_labels_for_forest(
    meta_analysis_stats[[disagree_example2]][[1]][,1]),
  main = paste(disagree_example2,
               "significant in untargeted, tar and untar disagree",sep="\n"),
  xlab = "Log fc",col = "blue")

Targeted vs. untargeted: comparison as a prediction task

Use 5-fold cross validation for analysis within tissues. For each pair of targeted and untargeted datasets from the same tissue, we use the untargeted data as the predictive features and all metabolites in the targeted datasets as the dependent variables. The code below uses feature selection and random forests to train the predictive models.

nfolds = 5
prediction_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
  nn1_tissue = strsplit(nn1,split=",")[[1]][1]
  nn1_tissue = gsub("_powder","",nn1_tissue)
  if(grepl("untargeted",nn1)){next}
  for(nn2 in names(metabolomics_processed_datasets)){
    if(nn2 == nn1){next}
    if(!grepl("untargeted",nn2)){next}
    nn2_tissue = strsplit(nn2,split=",")[[1]][1]
    nn2_tissue = gsub("_powder","",nn2_tissue)
    nn2_dataset = strsplit(nn2,split=",")[[1]][2]
    if(nn1_tissue!=nn2_tissue){next}
    print(paste("features from:",nn2))
    print(paste("labels from:",nn1))
    # get the numeric datasets and their annotation
    y = metabolomics_processed_datasets[[nn1]]$sample_data
    x = metabolomics_processed_datasets[[nn2]]$sample_data
    # align the sample sets
    bid_y = merged_dmaqc_data[colnames(y),"bid"]
    bid_x = merged_dmaqc_data[colnames(x),"bid"]    
    # step 1: merge samples from the same BID
    if(length(unique(bid_x))!=length(bid_x)){
      x = aggregate_repeated_samples(x,bid_x)
    }
    else{
      colnames(x) = bid_x
    }
    if(length(unique(bid_y))!=length(bid_y)){
      y = aggregate_repeated_samples(y,bid_y)
    }else{
      colnames(y) = bid_y
    }
    # step 2: use the shared bio ids
    shared_bids = as.character(intersect(colnames(y),colnames(x)))
    x = t(as.matrix(x[,shared_bids]))
    y = t(as.matrix(y[,shared_bids]))
    # At this point x and y are over the same BIDs, now we add the metadata
    y_meta = unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
    rownames(y_meta) = y_meta$bid
    y_meta = y_meta[shared_bids,]
    
    # take the covariates (ignore distances)
    curr_cov_cols = intersect(colnames(y_meta),biospec_cols[2])
    curr_covs = data.frame(y_meta[,curr_cov_cols])
    names(curr_covs) = curr_cov_cols
    curr_covs$sex = y_meta$animal.registration.sex # add sex
    # add the covariates into x
    x = cbind(x,curr_covs)
    
    # Run the regressions
    folds = sample(rep(1:nfolds,(1+nrow(x)/nfolds)))[1:nrow(x)]
    numFeatures = min(ncol(x),2000)
    preds = c();real=c()
    for(i in 1:ncol(y)){
      if( i %% 10 == 0){print(paste("analyzing metabolite number:",i))}
      y_i = y[,1]
      i_preds = c();i_real=c()
      for(j in 1:nfolds){
        tr_x = x[folds!=j,]
        tr_yi = y_i[folds!=j]
        te_x = x[folds==j,]
        te_y = y_i[folds==j]
        # random forest
        # model = randomForest(tr_yi,x=tr_x,ntree = 20)
        # te_preds = predict(model,newdata = te_x)
        model = feature_selection_wrapper(tr_x,tr_yi,
                   coeff_of_var,randomForest,
                   topK = numFeatures,ntree=50)
        te_preds = predict(model,newdata = te_x)
        i_preds = c(i_preds,te_preds)
        i_real = c(i_real,te_y)
      }
      preds = cbind(preds,i_preds)
      real = cbind(real,i_real)
    }
    colnames(preds) = colnames(y)
    colnames(reals) = colnames(y)
    currname = paste(nn1,nn2,sep=";")
    prediction_analysis_results[[currname]] = list(
      preds = preds,real=real
    )
  }
}
save_to_bucket(prediction_analysis_results,
               file="tar_vs_untar_prediction_analysis_results.RData",
               bucket = "gs://bic_data_analysis/pass1a/metabolomics/")

We now take the predicted and real values and estimate the prediction accuracy in different ways.

cut(CoVs,breaks = 2)
 [1] (0.478,0.69]  (0.478,0.69]  (0.265,0.478] (0.265,0.478] (0.265,0.478] (0.265,0.478]
 [7] (0.478,0.69]  (0.265,0.478] (0.265,0.478] (0.478,0.69] 
Levels: (0.265,0.478] (0.478,0.69]

We now present a few summary plots.

As additional references, we train below additional models. First, we check the prediction of naive models that use technical and clinical covariates only. Second, we use multi-task regression and deep learning models.

cov_prediction_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
  nn1_tissue = strsplit(nn1,split=",")[[1]][1]
  nn1_tissue = gsub("_powder","",nn1_tissue)
  if(grepl("untargeted",nn1)){next}
  print(nn1)
  y = metabolomics_processed_datasets[[nn1]]$sample_data
  y_vials = colnames(y)
  bid_y = merged_dmaqc_data[colnames(y),"bid"]
  colnames(y) = bid_y
  y = t(as.matrix(y))
  if(ncol(y)>1000){next}
  cov_cols = c("animal.registration.sex",
             "acute.test.weight",
             "acute.test.distance",
             "animal.key.timepoint")
  covs = merged_dmaqc_data[y_vials,cov_cols]
  x = covs
  
  # Run the regressions
  folds = sample(rep(1:nfolds,(1+nrow(x)/nfolds)))[1:nrow(x)]
  numFeatures = min(ncol(x),2000)
  preds = c();real=c()
  for(i in 1:ncol(y)){
    y_i = y[,1]
    i_preds = c();i_real=c()
    for(j in 1:nfolds){
      print(j)
      tr_x = x[folds!=j,]
      tr_yi = y_i[folds!=j]
      te_x = x[folds==j,]
      te_y = y_i[folds==j]
      # random forest
      model = randomForest(tr_yi,x=tr_x,ntree = 20)
      te_preds = predict(model,newdata = te_x)
      i_preds = c(i_preds,te_preds)
      i_real = c(i_real,te_y)
    }
    preds = cbind(preds,i_preds)
    real = cbind(real,i_real)
  }
  cov_prediction_analysis_results[[nn1]] = list(
      preds = preds,real=real
    )
}

# preds = c();real=c()
# for(j in 1:nfolds){
#   tr_x = x[folds!=j,]
#   tr_y = y[folds!=j,]
#   te_x = x[folds==j,]
#   te_y = y[folds==j,]
#   model = MTL_wrapper(tr_x,tr_y,type="Regression", Regularization="L21")
#   te_preds = predict(model,te_x)
#   real = rbind(real,te_y)
#   preds = rbind(preds,te_preds)
# }
# diag(cor(preds,real))

# Using PLS regression
# library(pls)
# pls_model = plsr(y~x,ncomp = 5,validation="LOO")
# eval = MSEP(pls_model)
# 
# y_pca = prcomp(y)
# plot(y_pca)
# explained_var = y_pca$sdev^2/sum(y_pca$sdev^2)
# y_pca_matrix = y_pca$x[,1:10]
# 
# # regress out sex, weight
# 
# get_explained_variance_using_PCA(x,y)
# x = apply(x,2,regress_out,covs=covs)
# y = apply(y,2,regress_out,covs=covs)
# get_explained_variance_using_PCA(x,y)
LS0tCnRpdGxlOiAiQklDIG1ldGFib2xvbWljcyBkYXRhIGFuYWx5c2lzIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6CiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwotLS0KCkluIHRoaXMgZG9jdW1lbnQgd2UgcHJlc2VudCB0aGUgam9pbnQgYW5hbHlzaXMgb2YgdGhlIFBBU1MxQSBtZXRhYm9sb21pY3MgZGF0YXNldHMuCgojIExvYWQgYWxsIGRhdGFzZXRzCgpMb2FkIHRoZSBkYXRhIGZyb20gdGhlIGNsb3VkLCBpbmNsdWRpbmc6IHBoZW5vdHlwaWMgZGF0YSwgbWV0YWJvbG9taWMgZGF0YXNldHMsIGFuZCBtZXRhYm9sb21pY3MgZGljdGlvbmFyeS4KCmBgYHtyLHJlc3VsdHM9J2hpZGUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0Kc291cmNlKCJ+L0Rlc2t0b3AvcmVwb3MvbW90cnBhYy1iaWMtbm9ybS1xYy90b29scy9zdXBlcnZpc2VkX25vcm1hbGl6YXRpb25fZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjLWJpYy1ub3JtLXFjL3Rvb2xzL3Vuc3VwZXJ2aXNlZF9ub3JtYWxpemF0aW9uX2Z1bmN0aW9ucy5SIikKc291cmNlKCJ+L0Rlc2t0b3AvcmVwb3MvbW90cnBhYy1iaWMtbm9ybS1xYy90b29scy9nY3BfZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjLWJpYy1ub3JtLXFjL3Rvb2xzL2Fzc29jaWF0aW9uX2FuYWx5c2lzX21ldGhvZHMuUiIpCnNvdXJjZSgifi9EZXNrdG9wL3JlcG9zL21vdHJwYWMtYmljLW5vcm0tcWMvdG9vbHMvZGF0YV9hdXhfZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjL3Rvb2xzL3ByZWRpY3Rpb25fbWxfdG9vbHMuUiIpCmxpYnJhcnkocmFuZG9tRm9yZXN0KSAjIGZvciBjbGFzc2lmaWNhdGlvbiB0ZXN0cwoKIyBMb2FkIHRoZSBkbWFxYyBkYXRhCm1lcmdlZF9kbWFxY19kYXRhID0gIGxvYWRfZnJvbV9idWNrZXQoIm1lcmdlZF9kbWFxY19kYXRhMjAxOS0xMC0xNS5SRGF0YSIsCiAgICAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvcGhlbm9fZG1hcWMvIixGKQptZXJnZWRfZG1hcWNfZGF0YSA9IG1lcmdlZF9kbWFxY19kYXRhW1sxXV0Kcm93bmFtZXMobWVyZ2VkX2RtYXFjX2RhdGEpID0gYXMuY2hhcmFjdGVyKG1lcmdlZF9kbWFxY19kYXRhJHZpYWxfbGFiZWwpCiMgZGVmaW5lIHRoZSB0aXNzdWUgdmFyaWFibGUKbWVyZ2VkX2RtYXFjX2RhdGEkdGlzc3VlID0gbWVyZ2VkX2RtYXFjX2RhdGEkc2FtcGxldHlwZWRlc2NyaXB0aW9uCiMgZGVmaW5lIHRoZSB0aW1lIHRvIGZyZWV6ZSB2YXJpYWJsZQptZXJnZWRfZG1hcWNfZGF0YSR0aW1lX3RvX2ZyZWV6ZSA9IG1lcmdlZF9kbWFxY19kYXRhJGNhbGN1bGF0ZWQudmFyaWFibGVzLnRpbWVfZGVhdGhfdG9fY29sbGVjdF9taW4gKyAKICBtZXJnZWRfZG1hcWNfZGF0YSRjYWxjdWxhdGVkLnZhcmlhYmxlcy50aW1lX2NvbGxlY3RfdG9fZnJlZXplX21pbgoKIyBjb2wgdGltZSB2cy4gY29udHJvbAojIGRmID0gZGF0YS5mcmFtZSgKIyAgIGJpZCA9IG1lcmdlZF9kbWFxY19kYXRhJGJpZCwKIyAgIGVkdGFfY29sX3RpbWUgPSBtZXJnZWRfZG1hcWNfZGF0YSRjYWxjdWxhdGVkLnZhcmlhYmxlcy5lZHRhX2NvbGxfdGltZSwKIyAgIHRpbWVfdG9fZnJlZXplID0gbWVyZ2VkX2RtYXFjX2RhdGEkdGltZV90b19mcmVlemUsCiMgICBpc19jb250cm9sID0gbWVyZ2VkX2RtYXFjX2RhdGEkYW5pbWFsLmtleS5pc19jb250cm9sLAojICAgdHAgPSBtZXJnZWRfZG1hcWNfZGF0YSRhbmltYWwua2V5LnRpbWVwb2ludCwKIyAgIHRpc3N1ZSA9IG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3Npbmcuc2FtcGxldHlwZWRlc2NyaXB0aW9uCiMgKQojIGRmID0gdW5pcXVlKGRmKQojIGJveHBsb3QoZWR0YV9jb2xfdGltZS8zNjAwIH4gaXNfY29udHJvbCxkZikKIyBib3hwbG90KGVkdGFfY29sX3RpbWUvMzYwMCAtIHRwIH4gaXNfY29udHJvbCxkZikKIyB3aWxjb3gudGVzdChlZHRhX2NvbF90aW1lLzM2MDAgfiBpc19jb250cm9sLGRmKQoKIyBibG9vZCBmcmVlemUgdGltZXMKYmxvb2Rfc2FtcGxlcyA9IAogIG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3Npbmcuc2FtcGxldHlwZWRlc2NyaXB0aW9uID09CiAgIkVEVEEgUGxhc21hIgpibG9vZF9mcmVlemVfdGltZSA9IAogIGFzLmRpZmZ0aW1lKG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3NpbmcudF9mcmVlemUsdW5pdHMgPSAibWlucyIpIC0KICBhcy5kaWZmdGltZShtZXJnZWRfZG1hcWNfZGF0YSRzcGVjaW1lbi5wcm9jZXNzaW5nLnRfZWR0YXNwaW4sdW5pdHM9Im1pbnMiKQpibG9vZF9mcmVlemVfdGltZSA9IGFzLm51bWVyaWMoYmxvb2RfZnJlZXplX3RpbWUpCnRpbWVfdG9fZnJlZXplID0gbWVyZ2VkX2RtYXFjX2RhdGEkdGltZV90b19mcmVlemVbYmxvb2Rfc2FtcGxlc10gPSAKICBibG9vZF9mcmVlemVfdGltZVtibG9vZF9zYW1wbGVzXQoKIyBMb2FkIG91ciBwYXJzZWQgbWV0YWJvbG9taWNzIGRhdGFzZXRzCm1ldGFib2xvbWljc19wYXJzZWRfZGF0YXNldHMgPSBsb2FkX2Zyb21fYnVja2V0KAogIGZpbGUgPSAibWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0c19wYXNzMWFfZXh0ZXJuYWwxLlJEYXRhIiwKICBidWNrZXQgPSAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvbWV0YWJvbG9taWNzLyIpW1sxXV0KCiMgIyAjIFJlYWQgdGhlIGRpY3Rpb25hcnkKIyBkaWN0X2J1Y2tldCA9CiMgICAiZ3M6Ly9tb3RycGFjLWV4dGVybmFsLXJlbGVhc2UxLXJlc3VsdHMvbWV0YWJvbG9taWNzX3RhcmdldGVkL21vdHJwYWNfbWV0YWJvbG9taWNzX2RhdGFfZGljdGlvbmFyeS12MS4xLjUudHh0IgojIGRpY3RfZG93bmxvYWQgPSBnZXRfc2luZ2xlX2ZpbGVfZnJvbV9idWNrZXRfdG9fbG9jYWxfZGlyKGRpY3RfYnVja2V0KQojIG1ldGFib2xvbWljc19kaWN0ID0gZnJlYWQoZGljdF9kb3dubG9hZFtbMV1dLGRhdGEudGFibGUgPSBGKQojIG1ldGFib2xvbWljc19kaWN0W2dyZXBsKCJnbHVjIixtZXRhYm9sb21pY3NfZGljdFssMl0saWdub3JlLmNhc2UgPSBUKSxdCmBgYApEZWZpbmUgdGhlIHZhcmlhYmxlcyB0byBiZSBhZGp1c3RlZCBmb3I6CgpgYGB7cn0KYmlvc3BlY19jb2xzID0gYygKICAiYWN1dGUudGVzdC5kaXN0YW5jZSIsCiAgImNhbGN1bGF0ZWQudmFyaWFibGVzLnRpbWVfdG9fZnJlZXplIiwKICAjICJjYWxjdWxhdGVkLnZhcmlhYmxlcy5lZHRhX2NvbGxfdGltZSIsICMgbm8gbmVlZCAtIHNlZSBjb2RlIGFib3ZlIGZvciBibG9vZAogICJiaWQiICMgcmVxdWlyZWQgZm9yIG1hdGNoaW5nIGRhdGFzZXRzCiAgKQpkaWZmZXJlbnRpYWxfYW5hbHlzaXNfY29scyA9IGMoCiAgImFuaW1hbC5yZWdpc3RyYXRpb24uc2V4IiwKICAiYW5pbWFsLmtleS50aW1lcG9pbnQiLAogICJhbmltYWwua2V5LmlzX2NvbnRyb2wiCikKcGlwZWxpbmVfcWNfY29scyA9IGMoInNhbXBsZV9vcmRlciIpCmBgYAoKIyBMb2ctdHJhbnNmb3JtOiBlZmZlY3Qgb24gdmFyaWFuY2UKClNvbWUgc2l0ZXMgZG8gbm90IHVzZSB0aGUgbG9nIHRyYW5zZm9ybWF0aW9uIG9uIHRoZWlyIGRhdGFzZXQuIEluIHRoaXMgc2VjdGlvbiB3ZSBwbG90IHRoZSBjb2VmZmljaWVudCBvZiB2YXJpYXRpb24gYXMgYSBmdW5jdGlvbiBvZiB0aGUgbWVhbiBpbnN0ZW5zaXR5LiBXZSB0YWtlIGEgc2luZ2xlIGRhdGFzZXQgYXMgYW4gZXhhbXBsZSB0byBzaG93IGhvdyBsb2ctdHJhbnNmb3JtZWQgZGF0YSBoYXZlIHJlZHVjZWQgZGVwZW5kZW5jeSBhbmQgc21vb3RoZXIgcGxvdHMuCgpBcyBhbiBhZGRpdGlvbmFsIGFuYWx5c2lzIHdlIGFsc28gcGxvdCB0aGUgbnVtYmVyIG9mIG1pc3NpbmcgdmFsdWVzIHBlciBtZXRhYm9saXRlIGFzIGEgZnVuY3Rpb24gb2YgaXRzIG1lYW4gaW50ZW5zaXR5LiBXZSBzaG93IHRoYXQgd2hpbGUgdGhlcmUgaXMgaGlnaCBjb3JyZWxhdGlvbiBzb21lIG1pc3NpbmcgdmFsdWVzIGFwcGVhciBpbiBmYWlyZWx5IGhpZ2ggaW50ZW5zaXRpZXMuIFRoaXMgaXMgaW1wb3J0YW50IGZvciBpbXB1dGF0aW9uIGFzIHNvbWUgc2l0ZXMgdXNlIHNvbWUgZml4ZWQgbG93IHZhbHVlIGluc3RlYWQgb2Yga25uIGltcHV0YXRpb24uCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CgojIFBsb3QgY3YgdnMgbWVhbnMKbGlicmFyeShncGxvdHMpCmQgPSBtZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzW1sid2hpdGVfYWRpcG9zZV9wb3dkZXIsbWV0YWJfdV9oaWxpY3Bvcyx1bm5hbWVkIl1dCmR4ID0gZCRzYW1wbGVfZGF0YQpDb1Y8LWZ1bmN0aW9uKHgpe3JldHVybihzZCh4LG5hLnJtID0gVCkvbWVhbih4LG5hLnJtPVQpKX0KZG1lYW5zID0gYXBwbHkoZHgsMSxtZWFuLG5hLnJtPVQpCkNvVnMgPSBhcHBseShkeCwxLENvVikKaW5kcyA9ICFpcy5uYShDb1ZzKQpkZiA9IGRhdGEuZnJhbWUoTWVhbl9pbnRlbnNpdHkgPSBkbWVhbnNbaW5kc10sQ29WID0gQ29Wc1tpbmRzXSkKcGxvdChDb1Z+TWVhbl9pbnRlbnNpdHksZGYsY2V4PTAuNSxwY2g9MjAsbWFpbj0iUmF3IGRhdGEiKQpsaW5lcyhsb3dlc3MoQ29Wfk1lYW5faW50ZW5zaXR5LGRmKSxsdHk9Mixsd2Q9Mixjb2w9ImJsdWUiKQoKIyBSZXBlYXQgYWZ0ZXIgbG9nMgpkeCA9IGxvZygxK2Qkc2FtcGxlX2RhdGEsYmFzZT0yKQpkbWVhbnMgPSBhcHBseShkeCwxLG1lYW4sbmEucm09VCkKQ29WcyA9IGFwcGx5KGR4LDEsQ29WKQppbmRzID0gIWlzLm5hKENvVnMpCmRmID0gZGF0YS5mcmFtZShNZWFuX2ludGVuc2l0eSA9IGRtZWFuc1tpbmRzXSxDb1YgPSBDb1ZzW2luZHNdKQpwbG90KENvVn5NZWFuX2ludGVuc2l0eSxkZixjZXg9MC41LHBjaD0yMCxtYWluPSJMb2cyIGRhdGEiKQpsaW5lcyhsb3dlc3MoQ29Wfk1lYW5faW50ZW5zaXR5LGRmKSxsdHk9Mixsd2Q9Mixjb2w9ImJsdWUiKQoKIyBQbG90IG51bWJlciBvZiBOQXMgdnMgaW50ZW5zaXR5IG1lYW4KZHggPSBsb2coMStkJHNhbXBsZV9kYXRhLGJhc2U9MikKZG1lYW5zID0gYXBwbHkoZHgsMSxtZWFuLG5hLnJtPVQpCm51bV9uYXMgPSByb3dTdW1zKGlzLm5hKGR4KSkKZGYgPSBkYXRhLmZyYW1lKE51bV9OQXMgPSBudW1fbmFzW2luZHNdLE1lYW5faW50ZW5zaXR5ID0gZG1lYW5zW2luZHNdKQpyaG8gPSBjb3IoZGYkTnVtX05BcyxkZiRNZWFuX2ludGVuc2l0eSxtZXRob2Q9InNwZWFybWFuIikKcmhvID0gZm9ybWF0KHJobyxkaWdpdHM9MikKcGxvdChOdW1fTkFzfk1lYW5faW50ZW5zaXR5LGRmLGNleD0wLjUscGNoPTIwLAogICAgIG1haW49cGFzdGUoIlNwZWFybWFuOiIscmhvKSkKCgpgYGAKCiMgRGF0YSBmaWx0ZXJpbmcgYW5kIG5vcm1hbGl6YXRpb24KCldlIGdvIG92ZXIgZWFjaCBkYXRhc2V0IGFuZCBtZXJnZSB0aGUgbmFtZWQgYW5kIHVubmFtZWQgc3VicGFydHMgb2YgdGhlIHVudGFyZ2V0ZWQgZGF0YXNldHMuIEZvciB0YXJnZXRlZCBkYXRhLCB3ZSBtZXJnZSBkYXRhc2V0cyB0aGF0IGFyZSBmcm9tIHRoZSBzYW1lIHNpdGUgYW5kIHRpc3N1ZS4KClRoZW4gdGhlIHByZXByb2Nlc3Npbmcgb2YgdGhlIG5ldyBtZXJnZWQgZGF0YXNldHMgaXMgYXMgZm9sbG93czogKDEpIGxvZyB0aGUgZGF0YSAodmFsdWVzIGFyZSByYXcgaW50ZW5zaXRpZXMpLCAoMikgcmVtb3ZlIHJvd3Mgd2l0aCAkPjIwXCUkIG1pc3NpbmcgdmFsdWVzLCBhbmQgKDMpIGltcHV0ZSBtaXNzaW5nIHZhbHVlcyB1c2luZyBrbm4gKGs9MTApLgoKSW1wb3J0YW50OiBkYXRhc2V0cyB0aGF0IGRvIG5vdCBoYXZlIHVuaXF1ZSBtZXRhYm9saXRlIG5hbWVzIG9yIHNhbXBsZSBpZHMgcHJvYmFibHkgaGFkIGVycm9ycyB3aGlsZSBwYXJzaW5nIGFuZCBhcmUgdGhlcmVmb3JlIGlnbm9yZWQuIEFsc28sIHVudGFyZ2V0ZWQgd2l0aCBmYWlsZWQgbWVyZ25pbmcgb2YgdGhlIHVubmFtZWQgYW5kIG5hbWVkIHN1YnNldHMgKGUuZy4sIGR1ZSB0byBkaWZmZXJlbnQgc2FtcGxlIGlkcykgYXJlIGlnbm9yZWQgYXMgd2VsbC4KCkZpbmFsbHkgd2UgYWRkIGFuIGFkZGl0aW9uYWwgdmVyc2lvbiBmb3IgZWFjaCBkYXRhc2V0IGJ5IGRpcmVjdGx5IHJlZ3Jlc3Npbmcgb3V0IHRoZSBzYW1wbGUgb3JkZXIgY29tcG9uZW50LiBTZWUgaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNDc1NzYwMy8gZm9yIG1vcmUgZGV0YWlscyAodHJlbmQgY29ycmVjdGlvbiBpcyBhbHNvIGNhbGxlZCBiYWNrZ3JvdW5kIGNvcnJlY3Rpb24sIGFuZCB0aGlzIGlzIGRvbmUgZm9yIGVhY2ggYmF0Y2ggc2VwYXJhdGVseSkuCgpgYGB7cixldmFsPUZBTFNFLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cyA9IGMoKQojIHNldCBhIGJpbmFyeSB2ZWN0b3IgaW5kaWNhdGluZyB3aXRoIGVudHJpZXMgdG8gc2tpcAptZXRhYm9sb21pY3NfcmF3X2RhdGFzZXRzX3NraXAgPSByZXAoRixsZW5ndGgobWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0cykpCm5hbWVzKG1ldGFib2xvbWljc19yYXdfZGF0YXNldHNfc2tpcCkgPSBuYW1lcyhtZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzKQp1bnRhcmdldGVkX21lcmdlX2Vycm9ycyA9IGxpc3QoKQpmb3IoY3Vycm5hbWUgaW4gbmFtZXMobWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0cykpewogIAogIGlmKG1ldGFib2xvbWljc19yYXdfZGF0YXNldHNfc2tpcFtjdXJybmFtZV0pe25leHR9CiAgCiAgYXJyID0gc3Ryc3BsaXQoY3Vycm5hbWUsc3BsaXQ9IiwiKVtbMV1dCiAgYXJyID0gYXJyWy1sZW5ndGgoYXJyKV0KICBuYW1lMSA9IHBhc3RlKHBhc3RlKGFycixjb2xsYXBzZT0iLCIpLCJuYW1lZCIsc2VwPSIsIikKICBuYW1lMiA9IHBhc3RlKHBhc3RlKGFycixjb2xsYXBzZT0iLCIpLCJ1bm5hbWVkIixzZXA9IiwiKQogIAogICMgSWYgZGF0YXNldCBpcyB1bnRhcmdldGVkLCBtZXJnZSBuYW1lZCBhbmQgdW5uYW1lZCwgdXBkYXRlIHRoZSBkYXRhc2V0CiAgIyBjdXJybmFtZS4gU2tpcCB0aGUgZGF0YXNldCBpZiB0aGUgbWVyZ2UgZmFpbHMsIGJ1dCBzdG9yZSBhbmQgcHJpbnQgdGhlIGVycnJvci4KICAjIElmIHRoZSBkYXRhc2V0IGlzIHRhcmdldGVkIC0gZ28gb3ZlciBhbGwgZGF0YXNldHMgd2l0aCB0aGUgc2FtZSB2aWFsIGxhYmVscyBhbmQgCiAgIyBtZXJnZSBpZiBwb3NzaWJsZS4gCiAgaWYobmFtZTIgJWluJSBuYW1lcyhtZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzKSl7CiAgICBkMSA9IG1ldGFib2xvbWljc19wYXJzZWRfZGF0YXNldHNbW25hbWUxXV0KICAgIGQyID0gbWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0c1tbbmFtZTJdXQogICAgbmV3ZCA9IE5VTEwKICAgIHRyeUNhdGNoKHsKICAgICAgbmV3ZCA9IG1lcmdlX25hbWVkX2FuZF91bm5hbWVkX21ldGFib2xvbWljc19kYXRhc2V0cyhkMSxkMixzdHJpY3QgPSBGKQogICAgICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHt9KSAjIGNhbiBhZGQgZXJyb3IgaGFuZGxpbmcgaGVyZQogICAgY3Vycm5hbWUgPSBwYXN0ZShwYXN0ZShhcnIsY29sbGFwc2U9IiwiKSwidW50YXJnZXRlZCIsc2VwPSIsIikKICAgIG1ldGFib2xvbWljc19yYXdfZGF0YXNldHNfc2tpcFtuYW1lMV0gPSBUCiAgICBtZXRhYm9sb21pY3NfcmF3X2RhdGFzZXRzX3NraXBbbmFtZTJdID0gVAogICAgaWYoaXMubnVsbChuZXdkKSl7CiAgICAgIHByaW50KHBhc3RlKCIjIFNraXBwaW5nIGRhdGFzZXQiLAogICAgICAgICAgICAgICAgICBjdXJybmFtZSwKICAgICAgICAgICAgICAgICAgImJlY2F1c2UgbWVyZ2luZyB0aGUgbmFtZWQgYW5kIHVubmFtZWQgc3Vic2V0cyBmYWlsZWQiKSkKICAgICAgbmV4dAogICAgfQogIH0KICBlbHNlewogICAgbWV0YWJvbG9taWNzX3Jhd19kYXRhc2V0c19za2lwW2N1cnJuYW1lXSA9IFQKICAgIG5ld2QgPSBtZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzW1tjdXJybmFtZV1dCiAgICBjdXJyX3ZpYWxzID0gY29sbmFtZXMobmV3ZCRzYW1wbGVfZGF0YSkKICAgIGN1cnJfc2l0ZSA9IHRvbG93ZXIodW5pcXVlKG5ld2Qkc2FtcGxlX21ldGEkc2l0ZSkpCiAgICBmb3IoYW5vdGhlcl9kYXRhc2V0IGluIG5hbWVzKG1ldGFib2xvbWljc19yYXdfZGF0YXNldHNfc2tpcCkpewogICAgICBpZihtZXRhYm9sb21pY3NfcmF3X2RhdGFzZXRzX3NraXBbYW5vdGhlcl9kYXRhc2V0XSl7bmV4dH0KICAgICAgYW5vdGhlcl9kID0gbWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0c1tbYW5vdGhlcl9kYXRhc2V0XV0KICAgICAgYW5vdGhlcl92aWFscyA9IGNvbG5hbWVzKGFub3RoZXJfZCRzYW1wbGVfZGF0YSkKICAgICAgYW5vdGhlcl9kX3NpdGUgPSB0b2xvd2VyKHVuaXF1ZShhbm90aGVyX2Qkc2FtcGxlX21ldGEkc2l0ZSkpCiAgICAgIGFub3RoZXJfc2hhcmVkX3ZpYWxzID0gaW50ZXJzZWN0KGFub3RoZXJfdmlhbHMsY3Vycl92aWFscykKICAgICAgaWYoY3Vycl9zaXRlID09IGFub3RoZXJfZF9zaXRlICYmCiAgICAgICAgbGVuZ3RoKGFub3RoZXJfc2hhcmVkX3ZpYWxzKT09bGVuZ3RoKGFub3RoZXJfdmlhbHMpICYmCiAgICAgICAgbGVuZ3RoKGFub3RoZXJfc2hhcmVkX3ZpYWxzKT09bGVuZ3RoKGN1cnJfdmlhbHMpCiAgICAgICl7CiAgICAgICAgICAjIGV4YW1pbmUgdGhlIG1ldGFkYXRhCiAgICAgICAgICBjdXJyX21ldGEgPSBuZXdkJHNhbXBsZV9tZXRhCiAgICAgICAgICBhbm90aGVyX21ldGEgPSBhbm90aGVyX2Qkc2FtcGxlX21ldGEKICAgICAgICAgICMgYXQgdGhpcyBwb2ludCB3ZSBrbm93IHRoYXQgYm90aCBkYXRhc2V0cyBoYXZlIHRoZQogICAgICAgICAgIyBzYW1lIHNhbXBsZXMsIGJ1dCBub3QgbmVjZXNzYXJpbHkgdGhlIHNhbWUgY29udHJvbHMKICAgICAgICAgICMgdGhlc2UgaGFzIGJlZW4gcGFydGlhbGx5IHN1Ym1pdHRlZCBhbmQgd2UgdGhlcmVmb3JlIG5lZWQgdG8KICAgICAgICAgICMgaW50ZXJzZWN0IGhlcmUKICAgICAgICAgIHNoYXJlZF9tZXRhX3NhbXBsZXMgPSBpbnRlcnNlY3Qocm93bmFtZXMoY3Vycl9tZXRhKSxyb3duYW1lcyhhbm90aGVyX21ldGEpKQogICAgICAgICAgIyBtYWtlIHN1cmUgdGhhdCB0aGUgdmlhbHMsIHR5cGVzLCBhbmQgb3JkZXIgYXJlIHRoZSBzYW1lCiAgICAgICAgICBpZihsZW5ndGgoc2hhcmVkX21ldGFfc2FtcGxlcykgPCAxIHx8CiAgICAgICAgICAgICFhbGwoY3Vycl9tZXRhW3NoYXJlZF9tZXRhX3NhbXBsZXMsMTozXT09CiAgICAgICAgICAgICAgICAgYW5vdGhlcl9tZXRhW3NoYXJlZF9tZXRhX3NhbXBsZXMsMTozXSkpewogICAgICAgICAgICBuZXh0ICMgY29udGludWUgaWYgY2Fubm90IG1lcmdlCiAgICAgICAgICB9CiAgICAgICAgICB0cnkoewogICAgICAgICAgICBuZXdkID0gbWVyZ2VfbmFtZWRfYW5kX3VubmFtZWRfbWV0YWJvbG9taWNzX2RhdGFzZXRzKAogICAgICAgICAgICAgIG5ld2QsYW5vdGhlcl9kLHN0cmljdCA9IEYpICMgbm8gc3RyaWN0IG1lcmdlIHRoYW5rcyB0byB0ZXN0IGFib3ZlCiAgICAgICAgICAgIG1ldGFib2xvbWljc19yYXdfZGF0YXNldHNfc2tpcFthbm90aGVyX2RhdGFzZXRdID0gVAogICAgICAgICAgfSkKICAgICAgfQogICAgfQogICAgCiAgICAjIHVwZGF0ZSB0aGUgbmFtZSBvZiB0aGUgZGF0YXNldAogICAgIyB3ZSBrZWVwIHRyYWNrIG9mIHRoZSBudW1iZXIgb2YgZGF0YXNldHMgZnJvbSB0aGUgZ2l2ZW4gc2l0ZSwgYXMgc29tZQogICAgIyBtZXJnZXMgbWF5IGZhaWwKICAgIGN1cnJuYW1lID0gY3Vycm5hbWUgPSBwYXN0ZShhcnJbMV0sY3Vycl9zaXRlLHNlcD0iLCIpCiAgICBudW1fY3Vycm5hbWUgPSBzdW0oZ3JlcGwoY3Vycm5hbWUsbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpKQogICAgY3Vycm5hbWUgPSBwYXN0ZShjdXJybmFtZSxudW1fY3Vycm5hbWUrMSxzZXA9IiwiKQogIH0KICAKICBwcmludChwYXN0ZSgiIyMjIyBBbmFseXppbmcgZGF0YSBmcm9tOiIsY3Vycm5hbWUpKQogIGN1cnJfZGF0YSAgPSBuZXdkJHNhbXBsZV9kYXRhCiAgY3Vycl9kYXRhMiA9IHJlY2FzdF9udW1lcmljX2RhdGFfZnJhbWUoY3Vycl9kYXRhKQogIGN1cnJfZGF0YV9tYXQgPSBhcy5tYXRyaXgoY3Vycl9kYXRhMikKICBwcmludChwYXN0ZSgibm8gZXJyb3JzIGluIHBhcnNpbmcgbnVtZXJpYyB2YWx1ZXM6IiwKICAgICAgICAgICAgICBhbGwoY3Vycl9kYXRhPT1jdXJyX2RhdGFfbWF0LG5hLnJtPVQpKSkKCiAgIyBmbG9vciB0aGUgZGF0YSBhdCAwCiAgY3Vycl9kYXRhX21hdFtjdXJyX2RhdGFfbWF0PDBdID0gMAogICMgS2VlcCBhIGxvZ2dlZCB2ZXJzaW9uIG9mIHRoZSBkYXRhCiAgY3Vycl9kYXRhX2xvZyA9IGxvZyhjdXJyX2RhdGFfbWF0KzEsYmFzZT0yKQogIAogICMgb3JnYW5pemUgdGhlIG1ldGFkYXRhCiAgY3Vycl9tZXRhID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoY3Vycl9kYXRhKSwKICAgICAgICB1bmlvbihiaW9zcGVjX2NvbHMsZGlmZmVyZW50aWFsX2FuYWx5c2lzX2NvbHMpXQogICMgcmVtb3ZlIG1ldGFkYXRhIHZhcmlhYmxlcyB3aXRoIHRvbyBtYW55IE5BcwogIG5hX2NvdW50cyA9IGFwcGx5KGlzLm5hKGN1cnJfbWV0YSksMixzdW0pCiAgY3Vycl9tZXRhID0gY3Vycl9tZXRhWyxuYV9jb3VudHMvbnJvdyhjdXJyX21ldGEpIDwgMC4xXQogIAogICMgTG9vayBhdCB0aGUgc2FtcGxlIG9yZGVyCiAgY3Vycl9vcmRlciA9IG5ld2Qkc2FtcGxlX21ldGFbcm93bmFtZXMoY3Vycl9tZXRhKSwic2FtcGxlX29yZGVyIl0KCiAgIyBvcmdhbml6ZSB0aGUgZGF0YXNldAogIGN1cnJfZGF0YSA9IGN1cnJfZGF0YVsscm93bmFtZXMoY3Vycl9tZXRhKV0KICBjdXJyX2RhdGFfbG9nID0gY3Vycl9kYXRhX2xvZ1sscm93bmFtZXMoY3Vycl9tZXRhKV0KICAjIHJlbW92ZSB6ZXJvIHZhcmlhbmNlIHJvd3Mgb3Igcm93cyB3aXRoIG1hbnkgTkFzCiAgcm93c190b19yZW0gPSAhKGFwcGx5KGN1cnJfZGF0YSwxLHNkLG5hLnJtPVQpPjApCiAgcGVyY2VudF9uYSA9IHJvd1N1bXMoaXMubmEoY3Vycl9kYXRhKSkgLyBuY29sKGN1cnJfZGF0YSkKICBwcmludChwYXN0ZSgibnVtYmVyIG9mIE5BIGNlbGxzOiIsc3VtKGlzLm5hKGN1cnJfZGF0YSkpKSkKICByb3dzX3RvX3JlbSA9IHJvd3NfdG9fcmVtIHwgcGVyY2VudF9uYSA+IDAuMgogICMgcmVtb3ZlIHJvd3Mgd2l0aCBubyBhbm5vdGF0aW9uCiAgcm93c190b19yZW0gPSByb3dzX3RvX3JlbSB8IG5ld2QkZGF0YV9yYXdfcm93bmFtZXMgPT0gIiIKICByb3dzX3RvX3JlbSA9IHJvd3NfdG9fcmVtIHwgbmV3ZCRkYXRhX3Jhd19yb3duYW1lcyA9PSAiLSIKICByb3dzX3RvX3JlbSA9IHJvd3NfdG9fcmVtIHwgbmV3ZCRkYXRhX3Jhd19yb3duYW1lcyA9PSAiXyIKICByb3dzX3RvX3JlbSA9IHJvd3NfdG9fcmVtIHwgaXMubmEobmV3ZCRkYXRhX3Jhd19yb3duYW1lcykKICAjIHJlbW92ZSByb3dzIHdpdGggZHVwbGljYXRlZCByZXByZXNlbnRhdGlvbgogIHJvd19kdXBsaWNhdGlvbnMgPSBuYW1lcyh3aGljaCh0YWJsZShuZXdkJGRhdGFfcmF3X3Jvd25hbWVzKT4xKSkKICBpZihhbnkobmV3ZCRkYXRhX3Jhd19yb3duYW1lc1shcm93c190b19yZW1dICVpbiUgcm93X2R1cGxpY2F0aW9ucykpewogICAgcm93c190b19yZW0gPSByb3dzX3RvX3JlbSB8IG5ld2QkZGF0YV9yYXdfcm93bmFtZXMgJWluJSByb3dfZHVwbGljYXRpb25zCiAgICBwcmludCgiIyBXQVJOSU5HOiBkYXRhc2V0IGhhcyBkdXBsaWNhdGVkIHJvdyBuYW1lcyB3aG9zZSBkYXRhIGFyZSBub3cgaWdub3JlZCIpCiAgICBwcmludChwYXN0ZShyb3dfZHVwbGljYXRpb25zLGNvbGxhcHNlPSIsIikpCiAgfQogIGN1cnJfZGF0YSA9IGN1cnJfZGF0YVshcm93c190b19yZW0sXQogIGN1cnJfZGF0YV9sb2cgPSBjdXJyX2RhdGFfbG9nWyFyb3dzX3RvX3JlbSxdCiAgIyBpbXB1dGUgLSB1c2UgY2FwdHVyZS5vdXRwdXQgdG8gYXZvaWQgcHJpbnRzCiAgY2FwdHVyZS5vdXRwdXQoCiAgICB7Y3Vycl9kYXRhX2ltcCA9IGltcHV0ZTo6aW1wdXRlLmtubihhcy5tYXRyaXgoY3Vycl9kYXRhX2xvZykpJGRhdGF9CiAgICAsZmlsZT1OVUxMKQogIGN1cnJfZGF0YV9ybmFtZXMgPSBuZXdkJGRhdGFfcmF3X3Jvd25hbWVzWyFyb3dzX3RvX3JlbV0KICByb3duYW1lcyhjdXJyX2RhdGFfaW1wKSA9IGN1cnJfZGF0YV9ybmFtZXMKICAKICAjIFJlZ3Jlc3Mgb3V0IHRoZSBzYW1wbGUgb3JkZXIKICAjIGxtX3JlZ3Jlc3NfbWF0cml4IHdvcmtzIG9uIGNvbHVtbnMKICBjdXJyX2RhdGFfaW1wMiA9IHQobG1fcmVncmVzc19vdXRfbWF0cml4KHQoY3Vycl9kYXRhX2ltcCksY3Vycl9vcmRlcikpCiAgCiAgIyB1cGRhdGUgdGhlIGRhdGEgb2JqZWN0CiAgbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbY3Vycm5hbWVdXSA9IG5ld2QKICBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tjdXJybmFtZV1dJHNhbXBsZV9kYXRhID0gY3Vycl9kYXRhX2ltcAogIG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW2N1cnJuYW1lXV0kc2FtcGxlX2RhdGFfbm9sb2dfbm9pbXAgPSBjdXJyX2RhdGEKICBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tjdXJybmFtZV1dJGRhdGFfcmF3X3Jvd25hbWVzID0gY3Vycl9kYXRhX3JuYW1lcwogIG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW2N1cnJuYW1lXV0kc2FtcGxlX21ldGFfcGFyc2VkID0gY3Vycl9tZXRhCiAgbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbY3Vycm5hbWVdXSRzYW1wbGVfZGF0YV9vcmRlcl9hZGogPSBjdXJyX2RhdGFfaW1wMgp9CgpzYXZlX3RvX2J1Y2tldChtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzLAogICAgICAgICAgICAgICBmaWxlPSJtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzMTAyODIwMTkuUkRhdGEiLAogICAgICAgICAgICAgICBidWNrZXQgPSAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvbWV0YWJvbG9taWNzLyIpCgpgYGAKCgpBbHRlcm5hdGl2ZWx5IGxvYWQgdGhlIHJlc3VsdCBmcm9tIHRoZSBidWNrZXQgdG8gc2F2ZSB0aW1lOgpgYGB7cn0KbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cyA9IGxvYWRfZnJvbV9idWNrZXQoCiAgZmlsZT0ibWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0czEwMjgyMDE5LlJEYXRhIiwKICBidWNrZXQgPSAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvbWV0YWJvbG9taWNzLyIKKVtbMV1dCmBgYAoKIyBMb2ctdHJhbnNmb3JtOiBlZmZlY3Qgb24gZGlmZmVyZW50aWFsIGFuYWx5c2lzCgpVbnRhcmdldGVkIGRhdGEgYXJlIHR5cGljYWxseSBsb2ctdHJhbnNmb3JtZWQgYW5kIGFuYWx5emVkIHVzaW5nIGxpbmVhciBtb2RlbHMuIE9uIHRoZSBvdGhlciBoYW5kLCBjb25jZW50cmF0aW9uIGRhdGEgYXJlIHNvbWV0aW1lcyBhbmFseXplZCB3aXRoIHRoZSBzYW1lIHR5cGUgb2YgbW9kZWxzIGJ1dCB1c2luZyB0aGUgb3JpZ2luYWwgZGF0YS4gVGhpcyByYWlzZXMgYSBwcm9ibGVtIGlmIHdlIHdpc2ggdG8gY29tcGFyZSBleGFjdCBzdGF0aXN0aWNzIGZyb20gdGhlc2UgZGF0YS4gSW4gdGhpcyBzZWN0aW9uIHdlIHBlcmZvcm0gcmVzaWR1YWwgYW5hbHlzaXMgZm9yIHNpbmdsZSBtZXRhYm9saXRlcy4gT3VyIGdvYWwgaXMgdG8gaWRlbnRpZnkgaWYgY29uY2VudHJhdGlvbiBkYXRhIGJlaGF2ZXMgIm5vcm1hbGx5IiB3aGVuIG5vdCBsb2ctdHJhbnNmb3JtZWQuIFRoZSBhbmFseXNpcyBiZWxvdyBleGFtaW5lcyB0aGUgcmVzaWR1YWxzIG9mIHRoZSBkYXRhIGFmdGVyIGZpdHRpbmcgbGluZWFyIG1vZGVscyBmb3IgZWFjaCBtZXRhYm9saXRlLCBhZGp1c3RpbmcgZm9yIGZyZWV6ZSB0aW1lIGFuZCBzZXguIFdlIHRoZW4gY29tcGFyZSB0aGUgcmVzdWx0cyB3aXRoIGFuZCB3aXRob3V0IHRoZSBsb2ctdHJhbnNmb3JtYXRpb24sIGNvdW50aW5nIHRoZSBudW1iZXIgb2YgbWV0YWJvbGl0ZXMgd2l0aCBhIHNpZ25pZmljYW50IGV2aWRlbmNlIGZvciBub24tbm9ybWFsbHkgZGlzdHJpYnV0ZWQgcmVzaWR1YWxzLiAKCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KIyBjaGVjayBmb3Igbm9ybWFsaXR5IHVzaW5nIHRoZSBLb2xtb2dvcm92LVNtaXJub3YgdGVzdAppc19ub3JtYWxfdGVzdDwtZnVuY3Rpb24odixzYW1wPTEwMDAwKXsKICByZXR1cm4oa3MudGVzdCh2LCJwbm9ybSIsbWVhbih2LG5hLnJtPVQpLHNkKHYsbmEucm0gPSBUKSkkcC52YWx1ZSkKfQojIGdvIG92ZXIgdGhlIG5hbWVkIGRhdGFzZXRzLCBnZXQgYSBsb2dnZWQgYW5kIGFuIHVubG9nZ2VkIHZlcnNpb24gb2YKIyB0aGUgZGF0YSwgdXNlIHRoZXNlIGFzIGlucHV0cyBmb3IgdGhlIHJlZ3Jlc3Npb24KcmVzaWR1YWxfYW5hbHlzaXNfcmVzdWx0cyA9IGxpc3QoKQpmb3Iobm4xIGluIG5hbWVzKG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMpKXsKICBpZihncmVwbCgidW50YXJnZXRlZCIsbm4xKSl7bmV4dH0KICB4X2xvZyA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJHNhbXBsZV9kYXRhCiAgeF91bmxvZyA9IDJeeF9sb2cKICAKICAjIHRha2UgdGhlIGNvdmFyaWF0ZXMsIGlnbm9yZSBkaXN0YW5jZXMKICB4X21ldGEgPSB1bmlxdWUobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kc2FtcGxlX21ldGFfcGFyc2VkKQogIGN1cnJfY292cyA9IHhfbWV0YVssaW50ZXJzZWN0KGNvbG5hbWVzKHhfbWV0YSksYmlvc3BlY19jb2xzWzJdKV0KICBjdXJyX2NvdnMgPSBkYXRhLmZyYW1lKGN1cnJfY292cywKICAgICAgICAgICBzZXg9eF9tZXRhJGFuaW1hbC5yZWdpc3RyYXRpb24uc2V4KQogIAogICMgZ2V0IHRoZSBsbSBvYmplY3RzCiAgY3Vycl9tb2RlbHMgPSBsaXN0KCkKICBmb3IodHAgaW4gdW5pcXVlKHhfbWV0YSRhbmltYWwua2V5LnRpbWVwb2ludCkpewogICAgICByZXNfbG9nID0gYXBwbHkoCiAgICAgICAgeF9sb2csMSwKICAgICAgICBwYXNzMWFfc2ltcGxlX2RpZmZlcmVudGlhbF9hYnVuZGFuY2UsCiAgICAgICAgdHBzID0geF9tZXRhJGFuaW1hbC5rZXkudGltZXBvaW50LHRwPXRwLAogICAgICAgIGlzX2NvbnRyb2wgPSB4X21ldGEkYW5pbWFsLmtleS5pc19jb250cm9sLAogICAgICAgIGNvdnMgPSBjdXJyX2NvdnMscmV0dXJuX21vZGVsPVQKICAgICAgKQogICAgICByZXNfdW5sb2cgPSBhcHBseSgKICAgICAgICB4X3VubG9nLDEsCiAgICAgICAgcGFzczFhX3NpbXBsZV9kaWZmZXJlbnRpYWxfYWJ1bmRhbmNlLAogICAgICAgIHRwcyA9IHhfbWV0YSRhbmltYWwua2V5LnRpbWVwb2ludCx0cD10cCwKICAgICAgICBpc19jb250cm9sID0geF9tZXRhJGFuaW1hbC5rZXkuaXNfY29udHJvbCwKICAgICAgICBjb3ZzID0gY3Vycl9jb3ZzLHJldHVybl9tb2RlbD1UCiAgICAgICkKICAgICAgaXNfbm9ybSA9IGNiaW5kKAogICAgICAgIHNhcHBseShyZXNfbG9nLGZ1bmN0aW9uKHgpaXNfbm9ybWFsX3Rlc3QocmVzaWR1YWxzKHgpKSksCiAgICAgICAgc2FwcGx5KHJlc191bmxvZyxmdW5jdGlvbih4KWlzX25vcm1hbF90ZXN0KHJlc2lkdWFscyh4KSkpCiAgICAgICkKICAgICAgY29sbmFtZXMoaXNfbm9ybSkgPSBjKCJsb2ciLCJub3QgbG9nIikKICAgICAgY3Vycl9tb2RlbHNbW2FzLmNoYXJhY3Rlcih0cCldXSA9IGlzX25vcm0KICB9CiAgcmVzaWR1YWxfYW5hbHlzaXNfcmVzdWx0c1tbbm4xXV0gPSBjdXJyX21vZGVscwp9CgojIElzIHRoZXJlIGEgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB0d28gb3B0aW9ucz8KbG9nX3ZzX3VubG9nX3N1bW1fbWF0ID0gc2FwcGx5KHJlc2lkdWFsX2FuYWx5c2lzX3Jlc3VsdHMsCiAgICBmdW5jdGlvbih4KXNhcHBseSh4LAogICAgICAgIGZ1bmN0aW9uKHkpCiAgICAgICAgICB3aWxjb3gudGVzdCh5WywxXSx5WywyXSxwYWlyZWQgPSBULGFsdGVybmF0aXZlID0gImciKSRwLnZhbHVlKSkKCiMgQ291bnQgdGhlIG51bWJlciBvZiBub24tbm9ybWFsIG1ldGFib2xpdGVzCm51bV9ub25ub3JtYWxfbG9nID0gc2FwcGx5KHJlc2lkdWFsX2FuYWx5c2lzX3Jlc3VsdHMsCiAgICBmdW5jdGlvbih4KXNhcHBseSh4LAogICAgICAgIGZ1bmN0aW9uKHkpc3VtKHlbLDFdPDAuMDUpKSkKbnVtX25vbm5vcm1hbF9sb2cgPSAKICBudW1fbm9ubm9ybWFsX2xvZ1ssb3JkZXIoY29sbmFtZXMobnVtX25vbm5vcm1hbF9sb2cpKV0KbnVtX25vbm5vcm1hbF91bmxvZyA9IHNhcHBseShyZXNpZHVhbF9hbmFseXNpc19yZXN1bHRzLAogICAgZnVuY3Rpb24oeClzYXBwbHkoeCwKICAgICAgICBmdW5jdGlvbih5KXN1bSh5WywyXTwwLjA1KSkpCm51bV9ub25ub3JtYWxfdW5sb2cgPSAKICBudW1fbm9ubm9ybWFsX3VubG9nWyxvcmRlcihjb2xuYW1lcyhudW1fbm9ubm9ybWFsX3VubG9nKSldCgpsaWJyYXJ5KGNvcnJwbG90KQpwYXIobWFyID0gYyg1LDUsNSwxMCkpCmNvcnJwbG90KHQobnVtX25vbm5vcm1hbF9sb2cpLSB0KG51bV9ub25ub3JtYWxfdW5sb2cpLAogICAgICAgICBpcy5jb3JyID0gRix0bC5jZXggPSAwLjcpCgoKYGBgCgoKIyBUcmFnZXRlZCB2cy4gVW50YXJnZXRlZDogc2luZ2xlIG1ldGFib2xpdGUgY29tcGFyaXNvbgoKIyMgQ29tcHV0ZSBzdGF0aXN0aWNzIGZvciBlYWNoIGRhdGFzZXQKCkNvbXBhcmUgb3ZlcmxhcHMsIGVmZmVjdCBzaXplcywgYW5kIGNvcnJlbGF0aW9ucyB3aXRoaW4gdGlzc3Vlcy4gQ29tcGFyZSB0YXJnZXRlZC11bnRhcmdldGVkIHBhaXJzIG9ubHkuIEZvciBkaWZmZXJlbnRpYWwgYW5hbHlzaXMgd2UgdXNlIHRoZSBzYW1lIG1vZGVsIGFzIGluIHRoZSBhbmFseXNpcyBhYm92ZS4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCiMgaGVscGVyIGZ1bmN0aW9uIHRvIHRyYW5zZm9ybSBhIG1ldGFib2xvbWljcyBtYXRyaXgKIyB0byB0aGF0IG9mIGl0cyBtb3RycGFjIGNvbXBvdW5kIGlkcwpleHRyYWN0X21ldGFiX2RhdGFfZnJvbV9yb3dfYW5ub3Q8LWZ1bmN0aW9uKHgscm93X2Fubm90X3gpewogICMgZ2V0IHRoZSBjb2xvdW1uIHRoYXQgaGFzIHRoZSByb3cgbmFtZXMKICBpbnRfc2l6ZXMgPSBhcHBseShyb3dfYW5ub3RfeCwyLGZ1bmN0aW9uKHgseSlsZW5ndGgoaW50ZXJzZWN0KHgseSkpLHk9cm93bmFtZXMoeCkpCiAgaW5kID0gd2hpY2goaW50X3NpemVzPT1tYXgoaW50X3NpemVzLG5hLnJtID0gVCkpWzFdCiAgcm93X2Fubm90X3ggPSByb3dfYW5ub3RfeFtpcy5lbGVtZW50KHJvd19hbm5vdF94WyxpbmRdLHNldD1yb3duYW1lcyh4KSksXQogIHJvd25hbWVzKHJvd19hbm5vdF94KSA9IHJvd19hbm5vdF94WyxpbmRdCiAgc2hhcmVkID0gaW50ZXJzZWN0KHJvd25hbWVzKHJvd19hbm5vdF94KSxyb3duYW1lcyh4KSkKICB4ID0geFtzaGFyZWQsXQogIHJvd19hbm5vdF94ID0gcm93X2Fubm90X3hbc2hhcmVkLF0KICByb3duYW1lcyh4KSA9IHJvd19hbm5vdF94JG1vdHJwYWNfY29tcF9uYW1lCiAgcmV0dXJuKHgpCn0KCnNpbmdsZV9tZXRhYm9saXRlX2NvcnJzID0gbGlzdCgpCnNpbmdsZV9tZXRhYm9saXRlX2RlID0gYygpCm5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzID0gbGlzdCgpCmZvcihubjEgaW4gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpewogIG5uMV90aXNzdWUgPSBzdHJzcGxpdChubjEsc3BsaXQ9IiwiKVtbMV1dWzFdCiAgbm4xX3Rpc3N1ZSA9IGdzdWIoIl9wb3dkZXIiLCIiLG5uMV90aXNzdWUpCiAgaWYoZ3JlcGwoInVudGFyZ2V0ZWQiLG5uMSkpe25leHR9CiAgc2luZ2xlX21ldGFib2xpdGVfY29ycnNbW25uMV1dID0gbGlzdCgpCiAgbmFtZWQyY292ZXJlZF9zaGFyZWRfbWV0YWJvbGl0ZXNbW25uMV1dID0gTlVMTAogIGZvcihubjIgaW4gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpewogICAgaWYobm4yID09IG5uMSl7bmV4dH0KICAgIGlmKCFncmVwbCgidW50YXJnZXRlZCIsbm4yKSl7bmV4dH0KICAgIG5uMl90aXNzdWUgPSBzdHJzcGxpdChubjIsc3BsaXQ9IiwiKVtbMV1dWzFdCiAgICBubjJfdGlzc3VlID0gZ3N1YigiX3Bvd2RlciIsIiIsbm4yX3Rpc3N1ZSkKICAgIG5uMl9kYXRhc2V0ID0gc3Ryc3BsaXQobm4yLHNwbGl0PSIsIilbWzFdXVsyXQogICAgaWYobm4xX3Rpc3N1ZSE9bm4yX3Rpc3N1ZSl7bmV4dH0KICAgICMgZ2V0IHRoZSBudW1lcmljIGRhdGFzZXRzIGFuZCB0aGVpciBhbm5vdGF0aW9uCiAgICB4ID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kc2FtcGxlX2RhdGEKICAgIHkgPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjJdXSRzYW1wbGVfZGF0YQogICAgcm93X2Fubm90X3ggPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjFdXSRyb3dfYW5ub3QKICAgIHJvd19hbm5vdF95ID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4yXV0kcm93X2Fubm90CiAgICAjIHRyYW5zZm9ybSBtZXRhYm9saXRlIG5hbWVzIHRvIHRoZSBtb3RycGFjIGNvbXAgbmFtZQogICAgeCA9IGV4dHJhY3RfbWV0YWJfZGF0YV9mcm9tX3Jvd19hbm5vdCh4LHJvd19hbm5vdF94KQogICAgeSA9IGV4dHJhY3RfbWV0YWJfZGF0YV9mcm9tX3Jvd19hbm5vdCh5LHJvd19hbm5vdF95KQogICAgIyBhbGlnbiB0aGUgc2FtcGxlIHNldHMKICAgIGJpZF95ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeSksImJpZCJdCiAgICBiaWRfeCA9IG1lcmdlZF9kbWFxY19kYXRhW2NvbG5hbWVzKHgpLCJiaWQiXSAgICAKICAgICMgc3RlcCAxOiBtZXJnZSBzYW1wbGVzIGZyb20gdGhlIHNhbWUgQklECiAgICBpZihsZW5ndGgodW5pcXVlKGJpZF94KSkhPWxlbmd0aChiaWRfeCkpewogICAgICB4ID0gYWdncmVnYXRlX3JlcGVhdGVkX3NhbXBsZXMoeCxiaWRfeCkKICAgIH0KICAgIGVsc2V7CiAgICAgIGNvbG5hbWVzKHgpID0gYmlkX3gKICAgIH0KICAgIGlmKGxlbmd0aCh1bmlxdWUoYmlkX3kpKSE9bGVuZ3RoKGJpZF95KSl7CiAgICAgIHkgPSBhZ2dyZWdhdGVfcmVwZWF0ZWRfc2FtcGxlcyh5LGJpZF95KQogICAgfWVsc2V7CiAgICAgIGNvbG5hbWVzKHkpID0gYmlkX3kKICAgIH0KICAgICMgc3RlcCAyOiB1c2UgdGhlIHNoYXJlZCBiaW8gaWRzCiAgICBzaGFyZWRfYmlkcyA9IGFzLmNoYXJhY3RlcihpbnRlcnNlY3QoY29sbmFtZXMoeSksY29sbmFtZXMoeCkpKQogICAgeCA9IGFzLm1hdHJpeCh4WyxzaGFyZWRfYmlkc10pCiAgICB5ID0gYXMubWF0cml4KHlbLHNoYXJlZF9iaWRzXSkKICAgICMgQXQgdGhpcyBwb2ludCB4IGFuZCB5IGFyZSBvdmVyIHRoZSBzYW1lIEJJRHMsIG5vdyB3ZSBhZGQgdGhlIG1ldGFkYXRhCiAgICB5X21ldGEgPSB1bmlxdWUobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kc2FtcGxlX21ldGFfcGFyc2VkKQogICAgcm93bmFtZXMoeV9tZXRhKSA9IHlfbWV0YSRiaWQKICAgIHlfbWV0YSA9IHlfbWV0YVtzaGFyZWRfYmlkcyxdCiAgICAKICAgICMgZ2V0IHRoZSBzaGFyZWQgbWF0ZWJvbGl0ZXMKICAgIHNoYXJlZF9tZXRhYm9saXRlcyA9IGludGVyc2VjdChyb3duYW1lcyh4KSxyb3duYW1lcyh5KSkKICAgIHNoYXJlZF9tZXRhYm9saXRlcyA9IG5hLm9taXQoc2hhcmVkX21ldGFib2xpdGVzKQogICAgaWYobGVuZ3RoKHNoYXJlZF9tZXRhYm9saXRlcyk9PTApe25leHR9CiAgICBuYW1lZDJjb3ZlcmVkX3NoYXJlZF9tZXRhYm9saXRlc1tbbm4xXV0gPSB1bmlvbigKICAgICAgbmFtZWQyY292ZXJlZF9zaGFyZWRfbWV0YWJvbGl0ZXNbW25uMV1dLAogICAgICBzaGFyZWRfbWV0YWJvbGl0ZXMKICAgICkKICAgIAogICAgIyBDb21wdXRlIHRoZSBjb3JyZWxhdGlvbiBtYXRyaWNlcyBvZiB0aGUgc2hhcmVkIG1ldGFib2xpdGVzCiAgICBpZihsZW5ndGgoc2hhcmVkX21ldGFib2xpdGVzKT4xKXsKICAgICAgICAgIGNvcnJzID1jb3IodCh4W3NoYXJlZF9tZXRhYm9saXRlcyxdKSwKICAgICAgICAgICAgICAgIHQoeVtzaGFyZWRfbWV0YWJvbGl0ZXMsXSksbWV0aG9kID0gInNwZWFybWFuIikKICAgIH0KICAgIGVsc2V7CiAgICAgICAgICBjb3JycyA9IGNvcih4W3NoYXJlZF9tZXRhYm9saXRlcyxdLAogICAgICAgICAgICAgICAgeVtzaGFyZWRfbWV0YWJvbGl0ZXMsXSxtZXRob2QgPSAic3BlYXJtYW4iKQogICAgfQogICAgCiAgICAjIHRha2UgdGhlIGNvdmFyaWF0ZXMgKGlnbm9yZSBkaXN0YW5jZXMpCiAgICBjdXJyX2Nvdl9jb2xzID0gaW50ZXJzZWN0KGNvbG5hbWVzKHlfbWV0YSksYmlvc3BlY19jb2xzWzJdKQogICAgY3Vycl9jb3ZzID0gZGF0YS5mcmFtZSh5X21ldGFbLGN1cnJfY292X2NvbHNdKQogICAgbmFtZXMoY3Vycl9jb3ZzKSA9IGN1cnJfY292X2NvbHMKICAgIGN1cnJfY292cyRzZXggPSB5X21ldGEkYW5pbWFsLnJlZ2lzdHJhdGlvbi5zZXggIyBhZGQgc2V4CiAgICAKICAgICMgZGlmZmVyZW50aWFsIGFuYWx5c2lzCiAgICBmb3IodHAgaW4gdW5pcXVlKHlfbWV0YSRhbmltYWwua2V5LnRpbWVwb2ludCkpewogICAgICByZXN4ID0gdChhcHBseSgKICAgICAgICBtYXRyaXgoeFtzaGFyZWRfbWV0YWJvbGl0ZXMsXSxucm93PWxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwxLAogICAgICAgIHBhc3MxYV9zaW1wbGVfZGlmZmVyZW50aWFsX2FidW5kYW5jZSwKICAgICAgICB0cHMgPSB5X21ldGEkYW5pbWFsLmtleS50aW1lcG9pbnQsdHA9dHAsCiAgICAgICAgaXNfY29udHJvbCA9IHlfbWV0YSRhbmltYWwua2V5LmlzX2NvbnRyb2wsCiAgICAgICAgY292cyA9IGN1cnJfY292cyxyZXR1cm5fbW9kZWw9RgogICAgICApKQogICAgICByZXN5ID0gdChhcHBseSgKICAgICAgICBtYXRyaXgoeVtzaGFyZWRfbWV0YWJvbGl0ZXMsXSxucm93PWxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwxLAogICAgICAgIHBhc3MxYV9zaW1wbGVfZGlmZmVyZW50aWFsX2FidW5kYW5jZSwKICAgICAgICB0cHMgPSB5X21ldGEkYW5pbWFsLmtleS50aW1lcG9pbnQsdHA9dHAsCiAgICAgICAgaXNfY29udHJvbCA9IHlfbWV0YSRhbmltYWwua2V5LmlzX2NvbnRyb2wsCiAgICAgICAgY292cyA9IGN1cnJfY292cyxyZXR1cm5fbW9kZWw9RgogICAgICApKQogICAgICAjIEFkZCBkYXRhc2V0IGluZm9ybWF0aW9uLCB0aW1lIHBvaW50LCB0aXNzdWUKICAgICAgIyBUaGVzZSBhcmUgaW1wb3J0YW50IGFubm90YXRpb25zIGZvciBvdXIgc3VtbWFyeSBtYXRyaXgKICAgICAgIyBjYWxsZWQgc2luZ2xlX21ldGFib2xpdGVfZGUgYmVsb3cKICAgICAgYWRkZWRfY29sdW1ucyA9IG1hdHJpeChjYmluZCgKICAgICAgICByZXAobm4xLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwKICAgICAgICByZXAobm4yLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwKICAgICAgICBzaGFyZWRfbWV0YWJvbGl0ZXMsCiAgICAgICAgcmVwKHRwLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwKICAgICAgICByZXAobm4xX3Rpc3N1ZSxsZW5ndGgoc2hhcmVkX21ldGFib2xpdGVzKSkKICAgICAgKSxucm93PWxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKQogICAgICByZXN4ID0gY2JpbmQocmVzeCxyZXAoVCxucm93KHJlc3gpKSkKICAgICAgY29sbmFtZXMocmVzeClbbmNvbChyZXN4KV0gPSAiaXNfdGFyZ2V0ZWQiCiAgICAgIHJlc3kgPSBjYmluZChyZXN5LHJlcChGLG5yb3cocmVzeSkpKQogICAgICBjb2xuYW1lcyhyZXN5KVtuY29sKHJlc3kpXSA9ICJpc190YXJnZXRlZCIKICAgICAgaWYobnJvdyhyZXN4KT4xKXsKICAgICAgICByZXN4ID0gY2JpbmQoYWRkZWRfY29sdW1uc1ssLTJdLHJlc3gpCiAgICAgICAgcmVzeSA9IGNiaW5kKGFkZGVkX2NvbHVtbnNbLC0xXSxyZXN5KQogICAgICB9CiAgICAgIGVsc2V7CiAgICAgICAgcmVzeCA9IGMoYWRkZWRfY29sdW1uc1ssLTJdLHJlc3gpCiAgICAgICAgcmVzeSA9IGMoYWRkZWRfY29sdW1uc1ssLTFdLHJlc3kpCiAgICAgIH0KICAgICAgc2luZ2xlX21ldGFib2xpdGVfZGUgPSByYmluZChzaW5nbGVfbWV0YWJvbGl0ZV9kZSxyZXN4KQogICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IHJiaW5kKHNpbmdsZV9tZXRhYm9saXRlX2RlLHJlc3kpCiAgICB9CiAgICAKICAgIHNpbmdsZV9tZXRhYm9saXRlX2NvcnJzW1tubjFdXVtbbm4yXV0gPSBjb3JycwogIH0KfQoKIyBSZWZvcm1hdCB0aGUgcmVzdWx0cyBmb3IgZWFzaWVyIGNvbXBhcmlzb24gbGF0ZXIKc2luZ2xlX21ldGFib2xpdGVfZGUgPSBkYXRhLmZyYW1lKHNpbmdsZV9tZXRhYm9saXRlX2RlKQpuYW1lcyhzaW5nbGVfbWV0YWJvbGl0ZV9kZSkgPSBjKAogICJkYXRhc2V0IiwibWV0YWJvbGl0ZSIsInRwIiwidGlzc3VlIiwKICAiRXN0IiwiU3RkIiwiVHN0YXQiLCJQdmFsdWUiLCJpc190YXJnZXRlZCIpCmZvcihjb2wgaW4gbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfZGUpWy1jKDE6NCldKXsKICBzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbY29sXV0gPSBhcy5udW1lcmljKAogICAgYXMuY2hhcmFjdGVyKHNpbmdsZV9tZXRhYm9saXRlX2RlW1tjb2xdXSkpCn0KZm9yKGNvbCBpbiBuYW1lcyhzaW5nbGVfbWV0YWJvbGl0ZV9kZSlbMTo0XSl7CiAgc2luZ2xlX21ldGFib2xpdGVfZGVbW2NvbF1dID0gCiAgICBhcy5jaGFyYWN0ZXIoc2luZ2xlX21ldGFib2xpdGVfZGVbW2NvbF1dKQp9CiMgUmVtb3ZlIGR1cGxpY2F0aW9ucwpyb3duYW1lcyhzaW5nbGVfbWV0YWJvbGl0ZV9kZSkgPSBOVUxMCmZvcihubiBpbiBuYW1lcyhzaW5nbGVfbWV0YWJvbGl0ZV9kZSkpewogIG5kaWcgPSA1CiAgaWYoZ3JlcGwoInB2YWwiLG5uLGlnbm9yZS5jYXNlID0gVCkpewogICAgbmRpZyA9IDEwCiAgfQogIGlmKGlzLm51bWVyaWMoc2luZ2xlX21ldGFib2xpdGVfZGVbW25uXV0pKXsKICAgIHNpbmdsZV9tZXRhYm9saXRlX2RlW1tubl1dID0gcm91bmQoc2luZ2xlX21ldGFib2xpdGVfZGVbW25uXV0sZGlnaXRzID0gbmRpZykKICB9Cn0Kc2luZ2xlX21ldGFib2xpdGVfZGUgPSB1bmlxdWUoc2luZ2xlX21ldGFib2xpdGVfZGUpCgojIGEgaGVscGVyIGZ1bmN0aW9uIGZvciBzaW1wbGlmeWluZyBkYXRhc2V0IG5hbWVzCnNpbXBsaWZ5X21ldGFiX2RhdGFzZXRfbmFtZTwtZnVuY3Rpb24ocyl7CiAgcyA9IGdzdWIoIm1ldGFiX3VfIiwiIixzKQogIHMgPSBnc3ViKCIsdW50YXJnZXRlZCIsIiIscykKICBzID0gZ3N1YigiLG5hbWVkIiwiIixzKQogIHJldHVybihzKQp9CnNpbmdsZV9tZXRhYm9saXRlX2RlWywxXSA9IHNhcHBseShzaW5nbGVfbWV0YWJvbGl0ZV9kZVssMV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaW1wbGlmeV9tZXRhYl9kYXRhc2V0X25hbWUpCgpgYGAKCldlIG5leHQgdHJhbnNmb3JtIHRoZSBkYXRhIGFib3ZlIGludG8gdGFibGVzIHRoYXQgY29udGFpbiBkYXRhIGZvciBlYWNoIGNvbWJpbmF0aW9uIG9mIG1ldGFib2xpdGUsIHRpbWUgcG9pbnQsIGFuZCB0aXNzdWUuIFRoZXNlIGFyZSB0aGVuIHVzZWQgZm9yIGRpZmZlcmVudCBtZXRhLWFuYWx5c2VzOiAoMSkgYSBzaW1wbGUgcmFuZG9tIGVmZmVjdHMgYW5hbHlzaXMsICgyKSByYW5kb20gZWZmZWN0cyB3aXRoIGEgYmluYXJ5IGNvdmFyaWF0ZSBpbmRpY2F0aW5nIGlmIGEgZGF0YXNldCBpcyB0YXJnZXRlZCBvciB1bnRhcmdldGVkLCAoMykgcmVkbyB0aGUgUkUgbW9kZWwgb2YgKDEpIHdpdGggdGhlIHRhcmdldGVkIGRhdGEgb25seSwgYW5kICg0KSByZWRvIHRoZSBSRSBtb2RlbCBvZiAoMSkgd2l0aCB0aGUgdW50YXJnZXRlZCBkYXRhIG9ubHkuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CgpsaWJyYXJ5KG1ldGFmb3IpCm1ldGFfYW5hbHlzaXNfc3RhdHMgPSBsaXN0KCkKZm9yKHRpc3N1ZSBpbiB1bmlxdWUoc2luZ2xlX21ldGFib2xpdGVfZGUkdGlzc3VlKSl7CiAgZm9yKHRwIGluIHVuaXF1ZShzaW5nbGVfbWV0YWJvbGl0ZV9kZSR0cCkpewogICAgY3Vycl9zdWJzZXQgPSBzaW5nbGVfbWV0YWJvbGl0ZV9kZVsKICAgICAgc2luZ2xlX21ldGFib2xpdGVfZGUkdGlzc3VlPT10aXNzdWUgJgogICAgICAgIHNpbmdsZV9tZXRhYm9saXRlX2RlJHRwPT10cCxdCiAgICBmb3IobWV0YWJvbGl0ZSBpbiB1bmlxdWUoY3Vycl9zdWJzZXQkbWV0YWJvbGl0ZSkpewogICAgICBjdXJyX21ldF9kYXRhID0gY3Vycl9zdWJzZXRbCiAgICAgICAgY3Vycl9zdWJzZXQkbWV0YWJvbGl0ZT09bWV0YWJvbGl0ZSxdCiAgICAgIGN1cnJfbWV0X2RhdGEkdmFyID0gY3Vycl9tZXRfZGF0YSRTdGReMgogICAgICByZV9tb2RlbDEgPSBOVUxMO3JlX21vZGVsMj1OVUxMCiAgICAgIHJlX21vZGVsX3RhciA9IE5VTEw7cmVfbW9kZWxfdW50YXIgPSBOVUxMCiAgICAgIHRyeSh7cmVfbW9kZWwxID0gcm1hLnVuaShjdXJyX21ldF9kYXRhJEVzdCxjdXJyX21ldF9kYXRhJHZhcil9KQogICAgICB0cnkoe3JlX21vZGVsMiA9IHJtYS5tdihjdXJyX21ldF9kYXRhJEVzdCxjdXJyX21ldF9kYXRhJHZhciwKICAgICAgICAgICAgICAgICAgICAgICAgIG1vZHM9Y3Vycl9tZXRfZGF0YSRpc190YXJnZXRlZCl9KQogICAgICB0cnkoe3JlX21vZGVsX3RhciA9IHJtYS51bmkoCiAgICAgICAgY3Vycl9tZXRfZGF0YVtjdXJyX21ldF9kYXRhJGlzX3RhcmdldGVkPT0xLCJFc3QiXSwKICAgICAgICBjdXJyX21ldF9kYXRhW2N1cnJfbWV0X2RhdGEkaXNfdGFyZ2V0ZWQ9PTEsInZhciJdCiAgICAgICl9KQogICAgICB0cnkoe3JlX21vZGVsX3VudGFyID0gcm1hLnVuaSgKICAgICAgICBjdXJyX21ldF9kYXRhW2N1cnJfbWV0X2RhdGEkaXNfdGFyZ2V0ZWQ9PTAsIkVzdCJdLAogICAgICAgIGN1cnJfbWV0X2RhdGFbY3Vycl9tZXRfZGF0YSRpc190YXJnZXRlZD09MCwidmFyIl0KICAgICAgKX0pCiAgICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbW3Bhc3RlKG1ldGFib2xpdGUsdGlzc3VlLHRwLHNlcD0iLCIpXV0gPSAKICAgICAgICBsaXN0KGN1cnJfbWV0X2RhdGE9Y3Vycl9tZXRfZGF0YSxyZV9tb2RlbDE9cmVfbW9kZWwxLAogICAgICAgICAgICByZV9tb2RlbDIgPSByZV9tb2RlbDIscmVfbW9kZWxfdGFyPXJlX21vZGVsX3RhciwKICAgICAgICAgICAgcmVfbW9kZWxfdW50YXIgPSByZV9tb2RlbF91bnRhcikKICAgIH0KICB9Cn0KCgpgYGAKCiMjIENvbXBhcmlzb24gcmVzdWx0cwoKV2Ugbm93IHNob3cgc29tZSBwbG90cyB0byBzdW1tYXJpemUgdGhlIGNvbXBhcmlzb24uIAoKIyMjIERhdGFzZXQgY292ZXJhZ2UgCgpXZSBmaXJzdCBwbG90IHRoZSBudW1iZXIgYW5kIHBlcmNlbnRhZ2Ugb2YgbWV0YWJvbGl0ZXMgaW4gdGhlIHRhcmdldGVkIGRhdGFzZXRzIHRoYXQgYXJlIG1lYXN1cmVkIGluIGF0IGxlYXN0IG9uZSB1bnRhcmdldGVkIGRhdGFzZXQuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoZ2dwbG90MikKZGF0YXNldDJudW1fbWV0YWJvbGl0ZXMgPSBzYXBwbHkobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpbnJvdyh4JHNhbXBsZV9kYXRhKSkKbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSA9IHNhcHBseShuYW1lZDJjb3ZlcmVkX3NoYXJlZF9tZXRhYm9saXRlcyxsZW5ndGgpCm5hbWVkX2RhdGFzZXRfY292ZXJhZ2UgPSBkYXRhLmZyYW1lKAogIG5hbWUgPSBuYW1lcyhuYW1lZF9kYXRhc2V0X2NvdmVyYWdlKSwKICBwZXJjZW50YWdlID0gbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSAvCiAgZGF0YXNldDJudW1fbWV0YWJvbGl0ZXNbbmFtZXMobmFtZWRfZGF0YXNldF9jb3ZlcmFnZSldLAogIGNvdW50ID0gbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSwKICB0b3RhbCA9IGRhdGFzZXQybnVtX21ldGFib2xpdGVzW25hbWVzKG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UpXQopCiMgYWRkIGRhdGFzZXRzIHdpdGggbm8gY292ZXJhZ2UKYWxsX3RhcmdldGVkX2RhdGFzZXRzID0gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykKYWxsX3RhcmdldGVkX2RhdGFzZXRzID0gYWxsX3RhcmdldGVkX2RhdGFzZXRzWyFncmVwbCgidW50YXJnIixhbGxfdGFyZ2V0ZWRfZGF0YXNldHMpXQp6ZXJvX2NvdmVyYWdlX2RhdGFzZXRzID0gc2V0ZGlmZihhbGxfdGFyZ2V0ZWRfZGF0YXNldHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UkbmFtZSkKemVyb19jb3ZlcmFnZV9kYXRhc2V0cyA9IGRhdGEuZnJhbWUoCiAgbmFtZSA9IHplcm9fY292ZXJhZ2VfZGF0YXNldHMsCiAgcGVyY2VudGFnZSA9IHJlcCgwLGxlbmd0aCh6ZXJvX2NvdmVyYWdlX2RhdGFzZXRzKSksCiAgY291bnQgPSByZXAoMCxsZW5ndGgoemVyb19jb3ZlcmFnZV9kYXRhc2V0cykpLAogIHRvdGFsID0gZGF0YXNldDJudW1fbWV0YWJvbGl0ZXNbemVyb19jb3ZlcmFnZV9kYXRhc2V0c10KKQpuYW1lZF9kYXRhc2V0X2NvdmVyYWdlID0gcmJpbmQobmFtZWRfZGF0YXNldF9jb3ZlcmFnZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgemVyb19jb3ZlcmFnZV9kYXRhc2V0cykKbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSA9IAogIG5hbWVkX2RhdGFzZXRfY292ZXJhZ2Vbb3JkZXIoYXMuY2hhcmFjdGVyKG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UkbmFtZSkpLF0KcHJpbnQoZ2dwbG90KG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UsIGFlcyh4PW5hbWUsIHk9cGVyY2VudGFnZSkpICsgCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsd2lkdGg9MC4yKSArIGNvb3JkX2ZsaXAoKSArCiAgZ2VvbV90ZXh0KGRhdGE9bmFtZWRfZGF0YXNldF9jb3ZlcmFnZSwgCiAgICAgICAgICAgIGFlcyhuYW1lLCBwZXJjZW50YWdlKzAuMDUsIGxhYmVsPWNvdW50KSwgCiAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGg9MC45KSwKICAgICAgICAgICAgc2l6ZT00KSArIAogIGdndGl0bGUoIlRhcmdldGVkIGRhdGFzZXQ6IGNvdmVyYWdlIGJ5IHVudGFyZ2V0ZWQiKSkKCmBgYAoKIyMjIFNwZWFybWFuIGNvcnJlbGF0aW9ucwoKV2UgZXhhbWluZSB0aGUgYXZlcmFnZSBhYnNvbHV0ZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSBwbGF0Zm9ybXMgKHdpdGhpbiB0aXNzdWVzKS4gV2hlbmV2ZXIgdHdvIHBsYXRmb3JtcyBzaGFyZSBtb3JlIHRoYW4gYSBzaW5nbGUgbWV0YWJvbGl0ZSB3ZSBwbG90IGJvdGggdGhlIGF2ZXJhZ2UgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgc2FtZSBtZXRhYm9saXRlcyBhbmQgYmV0d2VlbiBvdGhlciBtZXRhYm9saXRlcy4gQWRkaW5nIHRoZSBhdmVyYWdlIGNvcnJlbGF0aW9uIGJldHdlZW4gcGxhdGZvcm1zIGJ1dCB3aXRoIGRpZmZlcmVudCBtZXRhYm9saXRlcyBpcyBpbXBvcnRhbnQgYXMgaXQgZ2l2ZXMgc29tZSBwZXJzcGVjdGl2ZSB0byB3aGF0IGEgc2lnbmlmaWNhbnQgY29ycmVsYXRpb24gaXMuIFRoYXQgaXMsIGluIG1hbnkgY2FzZXMgYmVsb3csIHRoZSBhdmVyYWdlIGNvcnJlbGF0aW9uIG1heSBiZSBncmVhdGVyIHRoYW4gZXhwZWN0ZWQuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CiMgTmV4dCBleGFtaW5lIHRoZSBTcGVhcm1hbiBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwbGF0Zm9ybXMKbWVhbl9hYnM8LWZ1bmN0aW9uKHgsLi4uKXtyZXR1cm4obWVhbihhYnMoeCksLi4uKSl9CnNkX2FiczwtZnVuY3Rpb24oeCwuLi4pe3JldHVybihzZChhYnMoeCksLi4uKSl9CmV4dHJhY3RfZGlhZ192c19ub25fZGlhZzwtZnVuY3Rpb24oY29ycnMsZnVuYz1tZWFuX2FicywuLi4pewogIGlmKGxlbmd0aChjb3Jycyk9PTEpewogICAgcmV0dXJuKGMoc2FtZT1mdW5jKGNvcnJzLC4uLiksb3RoZXI9TkEpKQogIH0KICBzYW1lID0gZnVuYyhkaWFnKGNvcnJzKSwuLi4pCiAgb3RoZXIgPSBmdW5jKAogICAgYyhjb3Jyc1tsb3dlci50cmkoY29ycnMsZGlhZyA9IEYpXSksLi4uKQogIHJldHVybihjKHNhbWU9c2FtZSxvdGhlcj1vdGhlcikpCn0KCmZvcih0YXJfZGF0YXNldCBpbiBuYW1lcyhzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycykpewogIGwgPSBzaW5nbGVfbWV0YWJvbGl0ZV9jb3Jyc1tbdGFyX2RhdGFzZXRdXQogIGlmKGxlbmd0aChsKT09MCl7bmV4dH0KICBjb3JyX2luZm8gPSBhcy5kYXRhLmZyYW1lKHQoc2FwcGx5KGwsIGV4dHJhY3RfZGlhZ192c19ub25fZGlhZykpKQogIGNvcnJfc2QgPSBhcy5kYXRhLmZyYW1lKHQoc2FwcGx5KGwsIGV4dHJhY3RfZGlhZ192c19ub25fZGlhZyxmdW5jPXNkX2FicykpKQogIHJvd25hbWVzKGNvcnJfaW5mbykgPSBzYXBwbHkocm93bmFtZXMoY29ycl9pbmZvKSwKICAgICAgZnVuY3Rpb24oeClzdHJzcGxpdCh4LHNwbGl0PSIsIilbWzFdXVsyXSkKICByb3duYW1lcyhjb3JyX2luZm8pID0gZ3N1YigibWV0YWJfdV8iLCIiLHJvd25hbWVzKGNvcnJfaW5mbykpCiAgcm93bmFtZXMoY29ycl9zZCkgPSByb3duYW1lcyhjb3JyX2luZm8pCiAgY29ycl9pbmZvJGRhdGFzZXQgPSByb3duYW1lcyhjb3JyX2luZm8pCiAgY29ycl9zZCRkYXRhc2V0ID0gY29ycl9pbmZvJGRhdGFzZXQKICBjb3JyX2luZm8gPSBtZWx0KGNvcnJfaW5mbykKICBjb3JyX3NkID0gbWVsdChjb3JyX3NkKQogIGNvcnJfaW5mbyRzZCA9IGNvcnJfc2QkdmFsdWUKICBwcmludCgKICAgIGdncGxvdChjb3JyX2luZm8sIGFlcyh4PWRhdGFzZXQsIHk9dmFsdWUsIGZpbGw9dmFyaWFibGUpKSArCiAgICAgIGdlb21fYmFyKHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKCksIHN0YXQ9ImlkZW50aXR5IiwgY29sb3VyPSdibGFjaycpICsKICAgICAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj12YWx1ZS1zZCwgeW1heD12YWx1ZStzZCksbmEucm09VCwgCiAgICAgICAgICAgICAgICAgICB3aWR0aD0uMixwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSguOSkpICsKICAgIGdndGl0bGUodGFyX2RhdGFzZXQpICsgeGxhYigiVW50YXJnZXRlZCBkYXRhc2V0IikgKyB5bGFiKCJTcGVhcm1hbiIpICsKICAgICAgbGFicyhmaWxsID0gIlBhaXIgdHlwZSIpICsgCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0idG9wIixsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiKQogICkKfQpgYGAKCiMjIyBNZXRhLWFuYWx5c2lzIG9mIGRpZmZlcmVudGlhbCBlZmZlY3RzCgpIZXJlIHdlIGdvIGludG8gdGhlIHNpbmdsZSBtZXRhYm9saXRlcyBjb21wYXJpc29uIGluIGdyZWF0ZXIgZGV0YWlsIChhZ2Fpbiwgd2l0aGluIHRpc3N1ZXMpLiAKCldlIHN0YXJ0IHdpdGggYSBmZXcgZXhhbXBsZXMuIEhlcmUgYXJlIHRoZSByZXN1bHRzIGZvciBsYWN0YXRlIGluIHBsYXNtYS4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KbGFjdF9yZXMgPSBtZXRhX2FuYWx5c2lzX3N0YXRzWwogIGdyZXBsKCJsYWN0IixuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSxpZ25vcmUuY2FzZSA9IFQpICYKICAgIGdyZXBsKCJwbGFzbWEiLG5hbWVzKG1ldGFfYW5hbHlzaXNfc3RhdHMpLGlnbm9yZS5jYXNlID0gVCkKXQpsYWN0X3Jlc19ob3VycyA9IHNhcHBseShuYW1lcyhsYWN0X3JlcyksCiAgICAgICAgICAgIGZ1bmN0aW9uKHgpYXMubnVtZXJpYyhzdHJzcGxpdCh4LHNwbGl0PSIsIilbWzFdXVszXSkpCmxhY3RfcmVzID0gbGFjdF9yZXNbb3JkZXIobGFjdF9yZXNfaG91cnMpXQpmb3IobGFjdF9leGFtcGxlIGluIG5hbWVzKGxhY3RfcmVzKVsxOjZdKXsKICBjdXJyX2xhYmVscyA9IGdzdWIoInBsYXNtYSwiLCIiLAogICAgICAgICAgICAgICAgICAgICBtZXRhX2FuYWx5c2lzX3N0YXRzW1tsYWN0X2V4YW1wbGVdXVtbMV1dWywxXSkKICBmb3Jlc3QobWV0YV9hbmFseXNpc19zdGF0c1tbbGFjdF9leGFtcGxlXV0kcmVfbW9kZWwxLAogICAgICAgc2xhYiA9IGN1cnJfbGFiZWxzLAogICAgICAgbWFpbiA9IGxhY3RfZXhhbXBsZSx4bGFiID0gIkxvZyBmYyIsCiAgICAgICBjb2wgPSAiYmx1ZSIsY2V4ID0gMS4xKQp9CgpgYGAKCldlIGNhbiBub3cgY2hlY2sgdGhlIHNhbWUgYW5hbHlzaXMgZm9yIGxpdmVyOgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpsYWN0X3JlcyA9IG1ldGFfYW5hbHlzaXNfc3RhdHNbCiAgZ3JlcGwoImxhY3QiLG5hbWVzKG1ldGFfYW5hbHlzaXNfc3RhdHMpLGlnbm9yZS5jYXNlID0gVCkgJgogICAgZ3JlcGwoImxpdmVyIixuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSxpZ25vcmUuY2FzZSA9IFQpCl0KbGFjdF9yZXNfaG91cnMgPSBzYXBwbHkobmFtZXMobGFjdF9yZXMpLAogICAgICAgICAgICBmdW5jdGlvbih4KWFzLm51bWVyaWMoc3Ryc3BsaXQoeCxzcGxpdD0iLCIpW1sxXV1bM10pKQpsYWN0X3JlcyA9IGxhY3RfcmVzW29yZGVyKGxhY3RfcmVzX2hvdXJzKV0KZm9yKGxhY3RfZXhhbXBsZSBpbiBuYW1lcyhsYWN0X3JlcylbMTo2XSl7CiAgY3Vycl9sYWJlbHMgPSBnc3ViKCJsaXZlcl9wb3dkZXIsIiwiIiwKICAgICAgICAgICAgICAgICAgICAgbWV0YV9hbmFseXNpc19zdGF0c1tbbGFjdF9leGFtcGxlXV1bWzFdXVssMV0pCiAgZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2xhY3RfZXhhbXBsZV1dJHJlX21vZGVsMSwKICAgICAgIHNsYWIgPSBjdXJyX2xhYmVscywKICAgICAgIG1haW4gPSBsYWN0X2V4YW1wbGUseGxhYiA9ICJMb2cgZmMiLAogICAgICAgY29sID0gImJsdWUiLGNleCA9IDEuMSkKfQoKYGBgCgpXZSBub3cgbW92ZSB0byBhIHN5c3RlbWF0aWMgY29tcGFyaXNvbiBvZiB0aGUgZGF0YXNldHMuIFdlIHN0YXJ0IHdpdGggYSBuYWl2ZSBjb21wYXJpc29uLCBjaGVja2luZyBpZiBtZXRhYm9saXRlcyBhcmUgY29uc2lzdGVudGx5IGJlaW5nIGRpc2NvdmVyZWQgdXNpbmcgYSBzaW1wbGUgJHA9MC4wMDEkIHRocmVzaG9sZCBmb3Igc2lnbmlmaWNhbmNlLgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQojIE5haXZlIGNvbXBhcmlzb24KdGhyID0gMC4wMDEKbmFpdmVfYW5hbHlzaXNfdGFibGVzID0gbGFwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsCiAgICBmdW5jdGlvbih4KXRhYmxlKHgkY3Vycl9tZXRfZGF0YVssIlB2YWx1ZSJdPHRociwKICAgICAgICAgICAgICAgICAgICAgeCRjdXJyX21ldF9kYXRhWywiaXNfdGFyZ2V0ZWQiXSkpCnRhYmxlX3dpdGhfc2lnX3Jlc3VsdHMgPSBuYWl2ZV9hbmFseXNpc190YWJsZXNbCiAgc2FwcGx5KG5haXZlX2FuYWx5c2lzX3RhYmxlcyxucm93KT4xCl0KcGVyY2VudF93aXRoX3NpZyA9IAogIGxlbmd0aCh0YWJsZV93aXRoX3NpZ19yZXN1bHRzKS9sZW5ndGgobmFpdmVfYW5hbHlzaXNfdGFibGVzKQoKc2lnX3Jlc3VsdHMgPSBzYXBwbHkodGFibGVfd2l0aF9zaWdfcmVzdWx0cywKICAgICAgZnVuY3Rpb24oeClwYXN0ZSh4WyJUUlVFIixjKCIwIiwiMSIpXT4wLGNvbGxhcHNlPSIsIikpCnNpZ19yZXN1bHRzX2NvdW50cyA9IHRhYmxlKHNpZ19yZXN1bHRzKQoKcHJpbnQoIkNvdW50aW5nIHRoZSBudW1iZXIgb2YgbWV0YWJvbGl0ZXMgd2l0aCBwPDAuMDAxIikKcHJpbnQocGFzdGUoIlNpZ25pZmljYW50IGluIHRhcmdldGVkIG9ubHk6IixzaWdfcmVzdWx0c19jb3VudHNbIkZBTFNFLFRSVUUiXSkpCnByaW50KHBhc3RlKCJTaWduaWZpY2FudCBpbiB1bnRhcmdldGVkIG9ubHk6IixzaWdfcmVzdWx0c19jb3VudHNbIlRSVUUsRkFMU0UiXSkpCnByaW50KHBhc3RlKCJTaWduaWZpY2FudCBpbiBib3RoOiIsc2lnX3Jlc3VsdHNfY291bnRzWyJUUlVFLFRSVUUiXSkpCgpgYGAKCkFzIGEgbW9yZSByaWdvcm91cyBhbmFseXNpcywgd2UgdXNlIHRoZSBtZXRhLWFuYWx5c2VzIHRvIG9idGFpbiB1c2VmdWwgc3RhdGlzdGljcyBmb3IgY29tcGFyaXNvbi4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KIyBFeHRyYWN0IHNvbWUgdXNlZnVsIHN0YXRpc3RpY3MgcGVyIGFuYWx5c2lzCgojIFAtdmFsdWUgZm9yIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGFyZ2V0ZWQgYW5kIHVudGFyZ2V0ZWQKdGFyZ2V0ZWRfZGlmZl9wID0gCiAgc2FwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsZnVuY3Rpb24oeCl4JHJlX21vZGVsMiRwdmFsWzJdKQoKIyBQLXZhbHVlcyAtIHRhcmdldGVkIHZzLiB1bnRhcmdldGVkCnB2YWxzX3RhciA9IHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLGZ1bmN0aW9uKHgpeCRyZV9tb2RlbF90YXIkcHZhbCkKcHZhbHNfdW50YXIgPSBzYXBwbHkobWV0YV9hbmFseXNpc19zdGF0cyxmdW5jdGlvbih4KXgkcmVfbW9kZWxfdW50YXIkcHZhbCkKcHZhbHNfdW50YXIgPSB1bmxpc3QocHZhbHNfdW50YXJbc2FwcGx5KHB2YWxzX3VudGFyLGxlbmd0aCk+MF0pCnNpZ25pZmljYW50X2luID0gcmVwKCJOb25lIixsZW5ndGgocHZhbHNfdW50YXIpKQpzaWduaWZpY2FudF9pbltwdmFsc190YXI8MC4wMDFdID0gIlRhcmdldGVkIgpzaWduaWZpY2FudF9pbltwdmFsc191bnRhcjwwLjAwMV0gPSAiVW50YXJnZXRlZCIKc2lnbmlmaWNhbnRfaW5bcHZhbHNfdGFyPDAuMDAxICYgcHZhbHNfdW50YXI8MC4wMDFdID0gIkJvdGgiCnNpZ25pZmljYW50X2RpZmYgPSB0YXJnZXRlZF9kaWZmX3A8MC4wMDEKZGYgPSBkYXRhLmZyYW1lKAogIHRhcmdldGVkID0gLWxvZzEwKHB2YWxzX3RhciksCiAgdW50YXJnZXRlZCA9IC1sb2cxMChwdmFsc191bnRhciksCiAgc2lnbmlmaWNhbnRfaW4gPSBzaWduaWZpY2FudF9pbiwKICBzaWduaWZpY2FudF9kaWZmID0gc2lnbmlmaWNhbnRfZGlmZgopCnJobyA9IGNvcihwdmFsc190YXIscHZhbHNfdW50YXIsbWV0aG9kID0gInNwZWFybWFuIikKcmhvcCA9IGNvci50ZXN0KHB2YWxzX3RhcixwdmFsc191bnRhcixtZXRob2QgPSAic3BlYXJtYW4iKSRwLnZhbHVlCnByaW50KAogIGdncGxvdChkZiwgYWVzKHg9dGFyZ2V0ZWQsIHk9dW50YXJnZXRlZCwKICAgICAgICAgICAgICAgICBzaGFwZT1zaWduaWZpY2FudF9kaWZmLCBjb2xvcj1zaWduaWZpY2FudF9pbikpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZ3RpdGxlKHBhc3RlKCItbG9nMTAgcC12YWx1ZXMsIHNwZWFybWFuOiIsZm9ybWF0KHJobyxkaWdpdHM9MiksCiAgICAgICAgICAgICAgICAgICIocD0iLGZvcm1hdChyaG9wLGRpZ2l0cz0zKSwiKSIpKQopCgpwcmludCgiIyMjIFN1bW1hcnkgb2YgZGlmZmVyZW5jZXMgaW4gUkUgbW9kZWxzICMjIyIpCnByaW50KCJNb2RlbCBpcyBzaWduaWZpY2FudCBhdCBwPDAuMDAxOiIpCnByaW50KHRhYmxlKGRmJHNpZ25pZmljYW50X2luKSkKcHJpbnQoIkFkZGluZyBpc190YXJnZXRlZCBhcyBhIGNvdmFyaWF0ZSBoYXMgcDwwLjAwMToiKQpwcmludCh0YWJsZShkZiRzaWduaWZpY2FudF9kaWZmKSkKCiMgQmV0YXMgLSB0YXJnZXRlZCB2cy4gdW50YXJnZXRlZApiZXRhc190YXIgPSBzYXBwbHkobWV0YV9hbmFseXNpc19zdGF0cyxmdW5jdGlvbih4KXgkcmVfbW9kZWxfdGFyJGJldGFbMSwxXSkKYmV0YXNfdW50YXIgPSBzYXBwbHkobWV0YV9hbmFseXNpc19zdGF0cyxmdW5jdGlvbih4KXgkcmVfbW9kZWxfdW50YXIkYmV0YVsxLDFdKQpiZXRhc191bnRhciA9IHVubGlzdChiZXRhc191bnRhcltzYXBwbHkoYmV0YXNfdW50YXIsbGVuZ3RoKT4wXSkKZGYgPSBkYXRhLmZyYW1lKAogIHRhcmdldGVkID0gYmV0YXNfdGFyLAogIHVudGFyZ2V0ZWQgPSBiZXRhc191bnRhciwKICBzaWduaWZpY2FudF9pbiA9IHNpZ25pZmljYW50X2luLAogIHNpZ25pZmljYW50X2RpZmYgPSBzaWduaWZpY2FudF9kaWZmCikKcmhvID0gY29yKGJldGFzX3VudGFyLGJldGFzX3RhcixtZXRob2QgPSAic3BlYXJtYW4iKQpyaG9wID0gY29yLnRlc3QoYmV0YXNfdW50YXIsYmV0YXNfdGFyLG1ldGhvZCA9ICJzcGVhcm1hbiIpJHAudmFsdWUKcHJpbnQoCiAgZ2dwbG90KGRmLCBhZXMoeD10YXJnZXRlZCwgeT11bnRhcmdldGVkLAogICAgICAgICAgICAgICAgIHNoYXBlPXNpZ25pZmljYW50X2RpZmYsIGNvbG9yPXNpZ25pZmljYW50X2luKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdndGl0bGUocGFzdGUoIkVmZmVjdCBzaXplcywgc3BlYXJtYW46Iixmb3JtYXQocmhvLGRpZ2l0cz0yKSwKICAgICAgICAgICAgICAgICAgIihwPSIsZm9ybWF0KHJob3AsZGlnaXRzPTMpLCIpIikpCikKYGBgCgpGcm9tIHRoZSBwbG90cyBhYm92ZSB3ZSB0YWtlIHRoZSBtb3N0IGV4dHJlbWUgZXhhbXBsZXMgYW5kIGV4YW1pbmUgdGhlaXIgZm9yZXN0IHBsb3RzLgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQphZ3JlZV9leGFtcGxlID0gbmFtZXMoc2FtcGxlKHdoaWNoKHB2YWxzX3RhcjwgMWUtMTAgJiBwdmFsc191bnRhciA8IDFlLTEwICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhcmdldGVkX2RpZmZfcCA+IDAuMSkpWzFdKQpzaW1wbGlmeV9sYWJlbHNfZm9yX2ZvcmVzdDwtZnVuY3Rpb24ocyl7CiAgcyA9IGdzdWIoIix1bnRhcmdldGVkIiwiIixzKQogIHRpc3N1ZSA9IHN0cnNwbGl0KHMsc3BsaXQ9IiwiKVtbMV1dWzFdCiAgcyA9IGdzdWIocGFzdGUodGlzc3VlLCIsIixzZXA9IiIpLCIiLHMpCiAgcmV0dXJuKHMpCn0KZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2FncmVlX2V4YW1wbGVdXSRyZV9tb2RlbDEsCiAgc2xhYiA9IHNpbXBsaWZ5X2xhYmVsc19mb3JfZm9yZXN0KAogICAgbWV0YV9hbmFseXNpc19zdGF0c1tbYWdyZWVfZXhhbXBsZV1dW1sxXV1bLDFdKSwKICBtYWluID0gcGFzdGUoYWdyZWVfZXhhbXBsZSwic2lnbmlmaWNhbnQgaW4gYm90aCwgdGFyIGFuZCB1bnRhciBhZ3JlZSIsc2VwPSJcbiIpLAogIHhsYWIgPSAiTG9nIGZjIixjb2wgPSAiYmx1ZSIpCgphZ3JlZV9wX2Rpc2FncmVlX2JldGEgPSBuYW1lcyhzYW1wbGUod2hpY2gocHZhbHNfdGFyPCAxZS0xMCAmIHB2YWxzX3VudGFyIDwgMWUtMTAgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0ZWRfZGlmZl9wIDwgMC4wMDEpKVsxXSkKZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2FncmVlX3BfZGlzYWdyZWVfYmV0YV1dJHJlX21vZGVsMSwKICBzbGFiID0gc2ltcGxpZnlfbGFiZWxzX2Zvcl9mb3Jlc3QoCiAgICBtZXRhX2FuYWx5c2lzX3N0YXRzW1thZ3JlZV9wX2Rpc2FncmVlX2JldGFdXVtbMV1dWywxXSksCiAgbWFpbiA9IHBhc3RlKGFncmVlX3BfZGlzYWdyZWVfYmV0YSwKICAgICAgICAgICAgICAgInNpZ25pZmljYW50IGluIGJvdGgsIHRhciBhbmQgdW50YXIgZGlzYWdyZWUiLHNlcD0iXG4iKSwKICB4bGFiID0gIkxvZyBmYyIsY29sID0gImJsdWUiKQoKZGlzYWdyZWVfZXhhbXBsZTEgPSBuYW1lcyhzYW1wbGUod2hpY2gocHZhbHNfdGFyPCAxZS0yMCAmIHB2YWxzX3VudGFyID4wLjEpKVsxXSkKZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2Rpc2FncmVlX2V4YW1wbGUxXV0kcmVfbW9kZWwxLAogIHNsYWIgPSBzaW1wbGlmeV9sYWJlbHNfZm9yX2ZvcmVzdCgKICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbW2Rpc2FncmVlX2V4YW1wbGUxXV1bWzFdXVssMV0pLAogIG1haW4gPSBwYXN0ZShkaXNhZ3JlZV9leGFtcGxlMSwKICAgICAgICAgICAgICAgInNpZ25pZmljYW50IHRhcmdldGVkLCB0YXIgYW5kIHVudGFyIGRpc2FncmVlIixzZXA9IlxuIiksCiAgeGxhYiA9ICJMb2cgZmMiLGNvbCA9ICJibHVlIikKCgpkaXNhZ3JlZV9leGFtcGxlMiA9IG5hbWVzKHNhbXBsZSh3aGljaChwdmFsc190YXIgPiAwLjEgJiBwdmFsc191bnRhciA8IDFlLTIwKSlbMV0pCmZvcmVzdChtZXRhX2FuYWx5c2lzX3N0YXRzW1tkaXNhZ3JlZV9leGFtcGxlMl1dJHJlX21vZGVsMSwKICBzbGFiID0gc2ltcGxpZnlfbGFiZWxzX2Zvcl9mb3Jlc3QoCiAgICBtZXRhX2FuYWx5c2lzX3N0YXRzW1tkaXNhZ3JlZV9leGFtcGxlMl1dW1sxXV1bLDFdKSwKICBtYWluID0gcGFzdGUoZGlzYWdyZWVfZXhhbXBsZTIsCiAgICAgICAgICAgICAgICJzaWduaWZpY2FudCBpbiB1bnRhcmdldGVkLCB0YXIgYW5kIHVudGFyIGRpc2FncmVlIixzZXA9IlxuIiksCiAgeGxhYiA9ICJMb2cgZmMiLGNvbCA9ICJibHVlIikKCmBgYAoKCiMgVGFyZ2V0ZWQgdnMuIHVudGFyZ2V0ZWQ6IGNvbXBhcmlzb24gYXMgYSBwcmVkaWN0aW9uIHRhc2sKClVzZSA1LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBmb3IgYW5hbHlzaXMgd2l0aGluIHRpc3N1ZXMuIEZvciBlYWNoIHBhaXIgb2YgdGFyZ2V0ZWQgYW5kIHVudGFyZ2V0ZWQgZGF0YXNldHMgZnJvbSB0aGUgc2FtZSB0aXNzdWUsIHdlIHVzZSB0aGUgdW50YXJnZXRlZCBkYXRhIGFzIHRoZSBwcmVkaWN0aXZlIGZlYXR1cmVzIGFuZCBhbGwgbWV0YWJvbGl0ZXMgaW4gdGhlIHRhcmdldGVkIGRhdGFzZXRzIGFzIHRoZSBkZXBlbmRlbnQgdmFyaWFibGVzLiBUaGUgY29kZSBiZWxvdyB1c2VzIGZlYXR1cmUgc2VsZWN0aW9uIGFuZCByYW5kb20gZm9yZXN0cyB0byB0cmFpbiB0aGUgcHJlZGljdGl2ZSBtb2RlbHMuIAoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsZXZhbD1GQUxTRSxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9Cm5mb2xkcyA9IDUKcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzID0gbGlzdCgpCmZvcihubjEgaW4gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpewogIG5uMV90aXNzdWUgPSBzdHJzcGxpdChubjEsc3BsaXQ9IiwiKVtbMV1dWzFdCiAgbm4xX3Rpc3N1ZSA9IGdzdWIoIl9wb3dkZXIiLCIiLG5uMV90aXNzdWUpCiAgaWYoZ3JlcGwoInVudGFyZ2V0ZWQiLG5uMSkpe25leHR9CiAgZm9yKG5uMiBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKSl7CiAgICBpZihubjIgPT0gbm4xKXtuZXh0fQogICAgaWYoIWdyZXBsKCJ1bnRhcmdldGVkIixubjIpKXtuZXh0fQogICAgbm4yX3Rpc3N1ZSA9IHN0cnNwbGl0KG5uMixzcGxpdD0iLCIpW1sxXV1bMV0KICAgIG5uMl90aXNzdWUgPSBnc3ViKCJfcG93ZGVyIiwiIixubjJfdGlzc3VlKQogICAgbm4yX2RhdGFzZXQgPSBzdHJzcGxpdChubjIsc3BsaXQ9IiwiKVtbMV1dWzJdCiAgICBpZihubjFfdGlzc3VlIT1ubjJfdGlzc3VlKXtuZXh0fQogICAgcHJpbnQocGFzdGUoImZlYXR1cmVzIGZyb206IixubjIpKQogICAgcHJpbnQocGFzdGUoImxhYmVscyBmcm9tOiIsbm4xKSkKICAgICMgZ2V0IHRoZSBudW1lcmljIGRhdGFzZXRzIGFuZCB0aGVpciBhbm5vdGF0aW9uCiAgICB5ID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kc2FtcGxlX2RhdGEKICAgIHggPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjJdXSRzYW1wbGVfZGF0YQogICAgIyBhbGlnbiB0aGUgc2FtcGxlIHNldHMKICAgIGJpZF95ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeSksImJpZCJdCiAgICBiaWRfeCA9IG1lcmdlZF9kbWFxY19kYXRhW2NvbG5hbWVzKHgpLCJiaWQiXSAgICAKICAgICMgc3RlcCAxOiBtZXJnZSBzYW1wbGVzIGZyb20gdGhlIHNhbWUgQklECiAgICBpZihsZW5ndGgodW5pcXVlKGJpZF94KSkhPWxlbmd0aChiaWRfeCkpewogICAgICB4ID0gYWdncmVnYXRlX3JlcGVhdGVkX3NhbXBsZXMoeCxiaWRfeCkKICAgIH0KICAgIGVsc2V7CiAgICAgIGNvbG5hbWVzKHgpID0gYmlkX3gKICAgIH0KICAgIGlmKGxlbmd0aCh1bmlxdWUoYmlkX3kpKSE9bGVuZ3RoKGJpZF95KSl7CiAgICAgIHkgPSBhZ2dyZWdhdGVfcmVwZWF0ZWRfc2FtcGxlcyh5LGJpZF95KQogICAgfWVsc2V7CiAgICAgIGNvbG5hbWVzKHkpID0gYmlkX3kKICAgIH0KICAgICMgc3RlcCAyOiB1c2UgdGhlIHNoYXJlZCBiaW8gaWRzCiAgICBzaGFyZWRfYmlkcyA9IGFzLmNoYXJhY3RlcihpbnRlcnNlY3QoY29sbmFtZXMoeSksY29sbmFtZXMoeCkpKQogICAgeCA9IHQoYXMubWF0cml4KHhbLHNoYXJlZF9iaWRzXSkpCiAgICB5ID0gdChhcy5tYXRyaXgoeVssc2hhcmVkX2JpZHNdKSkKICAgICMgQXQgdGhpcyBwb2ludCB4IGFuZCB5IGFyZSBvdmVyIHRoZSBzYW1lIEJJRHMsIG5vdyB3ZSBhZGQgdGhlIG1ldGFkYXRhCiAgICB5X21ldGEgPSB1bmlxdWUobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kc2FtcGxlX21ldGFfcGFyc2VkKQogICAgcm93bmFtZXMoeV9tZXRhKSA9IHlfbWV0YSRiaWQKICAgIHlfbWV0YSA9IHlfbWV0YVtzaGFyZWRfYmlkcyxdCiAgICAKICAgICMgdGFrZSB0aGUgY292YXJpYXRlcyAoaWdub3JlIGRpc3RhbmNlcykKICAgIGN1cnJfY292X2NvbHMgPSBpbnRlcnNlY3QoY29sbmFtZXMoeV9tZXRhKSxiaW9zcGVjX2NvbHNbMl0pCiAgICBjdXJyX2NvdnMgPSBkYXRhLmZyYW1lKHlfbWV0YVssY3Vycl9jb3ZfY29sc10pCiAgICBuYW1lcyhjdXJyX2NvdnMpID0gY3Vycl9jb3ZfY29scwogICAgY3Vycl9jb3ZzJHNleCA9IHlfbWV0YSRhbmltYWwucmVnaXN0cmF0aW9uLnNleCAjIGFkZCBzZXgKICAgICMgYWRkIHRoZSBjb3ZhcmlhdGVzIGludG8geAogICAgeCA9IGNiaW5kKHgsY3Vycl9jb3ZzKQogICAgCiAgICAjIFJ1biB0aGUgcmVncmVzc2lvbnMKICAgIGZvbGRzID0gc2FtcGxlKHJlcCgxOm5mb2xkcywoMStucm93KHgpL25mb2xkcykpKVsxOm5yb3coeCldCiAgICBudW1GZWF0dXJlcyA9IG1pbihuY29sKHgpLDIwMDApCiAgICBwcmVkcyA9IGMoKTtyZWFsPWMoKQogICAgZm9yKGkgaW4gMTpuY29sKHkpKXsKICAgICAgaWYoIGkgJSUgMTAgPT0gMCl7cHJpbnQocGFzdGUoImFuYWx5emluZyBtZXRhYm9saXRlIG51bWJlcjoiLGkpKX0KICAgICAgeV9pID0geVssMV0KICAgICAgaV9wcmVkcyA9IGMoKTtpX3JlYWw9YygpCiAgICAgIGZvcihqIGluIDE6bmZvbGRzKXsKICAgICAgICB0cl94ID0geFtmb2xkcyE9aixdCiAgICAgICAgdHJfeWkgPSB5X2lbZm9sZHMhPWpdCiAgICAgICAgdGVfeCA9IHhbZm9sZHM9PWosXQogICAgICAgIHRlX3kgPSB5X2lbZm9sZHM9PWpdCiAgICAgICAgIyByYW5kb20gZm9yZXN0CiAgICAgICAgIyBtb2RlbCA9IHJhbmRvbUZvcmVzdCh0cl95aSx4PXRyX3gsbnRyZWUgPSAyMCkKICAgICAgICAjIHRlX3ByZWRzID0gcHJlZGljdChtb2RlbCxuZXdkYXRhID0gdGVfeCkKICAgICAgICBtb2RlbCA9IGZlYXR1cmVfc2VsZWN0aW9uX3dyYXBwZXIodHJfeCx0cl95aSwKICAgICAgICAgICAgICAgICAgIGNvZWZmX29mX3ZhcixyYW5kb21Gb3Jlc3QsCiAgICAgICAgICAgICAgICAgICB0b3BLID0gbnVtRmVhdHVyZXMsbnRyZWU9NTApCiAgICAgICAgdGVfcHJlZHMgPSBwcmVkaWN0KG1vZGVsLG5ld2RhdGEgPSB0ZV94KQogICAgICAgIGlfcHJlZHMgPSBjKGlfcHJlZHMsdGVfcHJlZHMpCiAgICAgICAgaV9yZWFsID0gYyhpX3JlYWwsdGVfeSkKICAgICAgfQogICAgICBwcmVkcyA9IGNiaW5kKHByZWRzLGlfcHJlZHMpCiAgICAgIHJlYWwgPSBjYmluZChyZWFsLGlfcmVhbCkKICAgIH0KICAgIGNvbG5hbWVzKHByZWRzKSA9IGNvbG5hbWVzKHkpCiAgICBjb2xuYW1lcyhyZWFscykgPSBjb2xuYW1lcyh5KQogICAgY3Vycm5hbWUgPSBwYXN0ZShubjEsbm4yLHNlcD0iOyIpCiAgICBwcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHNbW2N1cnJuYW1lXV0gPSBsaXN0KAogICAgICBwcmVkcyA9IHByZWRzLHJlYWw9cmVhbAogICAgKQogIH0KfQpzYXZlX3RvX2J1Y2tldChwcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHMsCiAgICAgICAgICAgICAgIGZpbGU9InRhcl92c191bnRhcl9wcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHMuUkRhdGEiLAogICAgICAgICAgICAgICBidWNrZXQgPSAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvbWV0YWJvbG9taWNzLyIpCmBgYAoKV2Ugbm93IHRha2UgdGhlIHByZWRpY3RlZCBhbmQgcmVhbCB2YWx1ZXMgYW5kIGVzdGltYXRlIHRoZSBwcmVkaWN0aW9uIGFjY3VyYWN5IGluIGRpZmZlcmVudCB3YXlzLiAKCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLGV2YWw9VFJVRSxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CgpwcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHMgPSAgbG9hZF9mcm9tX2J1Y2tldCgKICAidGFyX3ZzX3VudGFyX3ByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0cy5SRGF0YSIsCiAgICAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvbWV0YWJvbG9taWNzLyIsRikKcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzID0gcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzW1sxXV0KCnJlc3VsdHNfbWV0cmljcyA9IGxpc3QoKQpmb3Iobm4gaW4gbmFtZXMocHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzKSl7CiAgcHJlZHMgPSBwcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHNbW25uXV0kcHJlZHMKICByZWFsID0gcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzW1tubl1dJHJlYWwKICB0YXJfbmFtZSA9IHN0cnNwbGl0KG5uLHNwbGl0PSI7IilbWzFdXVsxXQogIHVudGFyX25hbWUgPSBzdHJzcGxpdChubixzcGxpdD0iOyIpW1sxXV1bMl0KICB5ID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbdGFyX25hbWVdXSRzYW1wbGVfZGF0YQogIGNvbG5hbWVzKHByZWRzKSA9IHJvd25hbWVzKHkpCiAgY29sbmFtZXMocmVhbCkgPSByb3duYW1lcyh5KQogIHRhcl9uYW1lID0gc2ltcGxpZnlfbWV0YWJfZGF0YXNldF9uYW1lKHRhcl9uYW1lKQogIHVudGFyX25hbWUgPSBzaW1wbGlmeV9tZXRhYl9kYXRhc2V0X25hbWUodW50YXJfbmFtZSkKICBjdXJydGlzc3VlID0gc3Ryc3BsaXQodGFyX25hbWUsc3BsaXQ9IiwiKVtbMV1dWzFdCiAgdGFyX25hbWUgPSBnc3ViKHBhc3RlKGN1cnJ0aXNzdWUsIiwiLHNlcD0iIiksIiIsdGFyX25hbWUpCiAgdW50YXJfbmFtZSA9IGdzdWIocGFzdGUoY3VycnRpc3N1ZSwiLCIsc2VwPSIiKSwiIix1bnRhcl9uYW1lKQogIGlmKCEgY3VycnRpc3N1ZSAlaW4lIG5hbWVzKHJlc3VsdHNfbWV0cmljcykpewogICAgcmVzdWx0c19tZXRyaWNzW1tjdXJydGlzc3VlXV0gPSBsaXN0KCkKICB9CiAgaWYoISB0YXJfbmFtZSAlaW4lIG5hbWVzKHJlc3VsdHNfbWV0cmljc1tbY3VycnRpc3N1ZV1dKSl7CiAgICByZXN1bHRzX21ldHJpY3NbW2N1cnJ0aXNzdWVdXVtbdGFyX25hbWVdXSA9IGxpc3QoKQogIH0KICAKICByaG9zID0gZm9ybWF0KGRpYWcoY29yKHByZWRzLHJlYWwsbWV0aG9kPSJzcGVhcm1hbiIpKSxkaWdpdHM9MykKICByaG9zID0gYXMubnVtZXJpYyhyaG9zKQogIFNFcyA9IGNvbFN1bXMoKHByZWRzLXJlYWwpXjIpCiAgTVNFcyA9IFNFcyAvIG5yb3cocHJlZHMpCiAgUk1TRSA9IHNxcnQoTVNFcykKICByTVNFID0gTVNFcyAvIGFwcGx5KHksMSx2YXIpCiAgQ29WcyA9IGFwcGx5KHksMSxzZCkgLyBhcHBseSh5LDEsbWVhbikKICBkaXNjQ29WcyA9IGN1dChDb1ZzLGJyZWFrcyA9IDIsb3JkZXJlZF9yZXN1bHQgPSBUKQogIAogIHJlc3VsdHNfbWV0cmljc1tbY3VycnRpc3N1ZV1dW1t0YXJfbmFtZV1dW1t1bnRhcl9uYW1lXV0gPSBkYXRhLmZyYW1lKAogICAgcmhvcyxNU0VzLFJNU0Usck1TRSxDb1ZzLGRpc2NDb1ZzCiAgKQp9CgpgYGAKCldlIG5vdyBwcmVzZW50IGEgZmV3IHN1bW1hcnkgcGxvdHMuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxldmFsPVRSVUUsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpmb3IodGlzc3VlIGluIG5hbWVzKHJlc3VsdHNfbWV0cmljcykpewogIGZvcih0YXIgaW4gbmFtZXMocmVzdWx0c19tZXRyaWNzW1t0aXNzdWVdXSkpewogICAgbCA9IHJlc3VsdHNfbWV0cmljc1tbdGlzc3VlXV1bW3Rhcl1dCiAgICByaG9fdnNfY3YgPSBjKCkKICAgIGZvcih1bnRhciBpbiBuYW1lcyhsKSl7CiAgICAgIG0gPSBsW1t1bnRhcl1dWyxjKCJyaG9zIiwiZGlzY0NvVnMiKV0gIyB0YWtlIHRoZSBjdXJyZW50IG1hdHJpeAogICAgICBtID0gY2JpbmQocmVwKHVudGFyLG5yb3cobSkpLG0pCiAgICAgIG0kZGlzY0NvVnMgPSBhcy5udW1lcmljKG0kZGlzY0NvVnMpCiAgICAgIHJob192c19jdiA9IHJiaW5kKHJob192c19jdixtKQogICAgfQogICAgY29sbmFtZXMocmhvX3ZzX2N2KVsxXSA9ICJkYXRhc2V0IgogICAgYm94cGxvdChyaG9zfmRpc2NDb1ZzOmRhdGFzZXQsZGF0YT1yaG9fdnNfY3YsbGFzPTIsCiAgICAgICAgICAgIHlsYWI9IlNwZWFybWFuIix4bGFiID0gIiIsCiAgICAgICAgICAgIG1haW4gPSBwYXN0ZSh0aXNzdWUsdGFyLHNlcD0iLCIpKQogIH0KfQpgYGAKCkFzIGFkZGl0aW9uYWwgcmVmZXJlbmNlcywgd2UgdHJhaW4gYmVsb3cgYWRkaXRpb25hbCBtb2RlbHMuIEZpcnN0LCB3ZSBjaGVjayB0aGUgcHJlZGljdGlvbiBvZiBuYWl2ZSBtb2RlbHMgdGhhdCB1c2UgdGVjaG5pY2FsIGFuZCBjbGluaWNhbCBjb3ZhcmlhdGVzIG9ubHkuIFNlY29uZCwgd2UgdXNlIG11bHRpLXRhc2sgcmVncmVzc2lvbiBhbmQgZGVlcCBsZWFybmluZyBtb2RlbHMuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxldmFsPUZBTFNFLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCmNvdl9wcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHMgPSBsaXN0KCkKZm9yKG5uMSBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKSl7CiAgbm4xX3Rpc3N1ZSA9IHN0cnNwbGl0KG5uMSxzcGxpdD0iLCIpW1sxXV1bMV0KICBubjFfdGlzc3VlID0gZ3N1YigiX3Bvd2RlciIsIiIsbm4xX3Rpc3N1ZSkKICBpZihncmVwbCgidW50YXJnZXRlZCIsbm4xKSl7bmV4dH0KICBwcmludChubjEpCiAgeSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJHNhbXBsZV9kYXRhCiAgeV92aWFscyA9IGNvbG5hbWVzKHkpCiAgYmlkX3kgPSBtZXJnZWRfZG1hcWNfZGF0YVtjb2xuYW1lcyh5KSwiYmlkIl0KICBjb2xuYW1lcyh5KSA9IGJpZF95CiAgeSA9IHQoYXMubWF0cml4KHkpKQogIGlmKG5jb2woeSk+MTAwMCl7bmV4dH0KICBjb3ZfY29scyA9IGMoImFuaW1hbC5yZWdpc3RyYXRpb24uc2V4IiwKICAgICAgICAgICAgICJhY3V0ZS50ZXN0LndlaWdodCIsCiAgICAgICAgICAgICAiYWN1dGUudGVzdC5kaXN0YW5jZSIsCiAgICAgICAgICAgICAiYW5pbWFsLmtleS50aW1lcG9pbnQiKQogIGNvdnMgPSBtZXJnZWRfZG1hcWNfZGF0YVt5X3ZpYWxzLGNvdl9jb2xzXQogIHggPSBjb3ZzCiAgCiAgIyBSdW4gdGhlIHJlZ3Jlc3Npb25zCiAgZm9sZHMgPSBzYW1wbGUocmVwKDE6bmZvbGRzLCgxK25yb3coeCkvbmZvbGRzKSkpWzE6bnJvdyh4KV0KICBudW1GZWF0dXJlcyA9IG1pbihuY29sKHgpLDIwMDApCiAgcHJlZHMgPSBjKCk7cmVhbD1jKCkKICBmb3IoaSBpbiAxOm5jb2woeSkpewogICAgeV9pID0geVssMV0KICAgIGlfcHJlZHMgPSBjKCk7aV9yZWFsPWMoKQogICAgZm9yKGogaW4gMTpuZm9sZHMpewogICAgICBwcmludChqKQogICAgICB0cl94ID0geFtmb2xkcyE9aixdCiAgICAgIHRyX3lpID0geV9pW2ZvbGRzIT1qXQogICAgICB0ZV94ID0geFtmb2xkcz09aixdCiAgICAgIHRlX3kgPSB5X2lbZm9sZHM9PWpdCiAgICAgICMgcmFuZG9tIGZvcmVzdAogICAgICBtb2RlbCA9IHJhbmRvbUZvcmVzdCh0cl95aSx4PXRyX3gsbnRyZWUgPSAyMCkKICAgICAgdGVfcHJlZHMgPSBwcmVkaWN0KG1vZGVsLG5ld2RhdGEgPSB0ZV94KQogICAgICBpX3ByZWRzID0gYyhpX3ByZWRzLHRlX3ByZWRzKQogICAgICBpX3JlYWwgPSBjKGlfcmVhbCx0ZV95KQogICAgfQogICAgcHJlZHMgPSBjYmluZChwcmVkcyxpX3ByZWRzKQogICAgcmVhbCA9IGNiaW5kKHJlYWwsaV9yZWFsKQogIH0KICBjb3ZfcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzW1tubjFdXSA9IGxpc3QoCiAgICAgIHByZWRzID0gcHJlZHMscmVhbD1yZWFsCiAgICApCn0KCiMgcHJlZHMgPSBjKCk7cmVhbD1jKCkKIyBmb3IoaiBpbiAxOm5mb2xkcyl7CiMgICB0cl94ID0geFtmb2xkcyE9aixdCiMgICB0cl95ID0geVtmb2xkcyE9aixdCiMgICB0ZV94ID0geFtmb2xkcz09aixdCiMgICB0ZV95ID0geVtmb2xkcz09aixdCiMgICBtb2RlbCA9IE1UTF93cmFwcGVyKHRyX3gsdHJfeSx0eXBlPSJSZWdyZXNzaW9uIiwgUmVndWxhcml6YXRpb249IkwyMSIpCiMgICB0ZV9wcmVkcyA9IHByZWRpY3QobW9kZWwsdGVfeCkKIyAgIHJlYWwgPSByYmluZChyZWFsLHRlX3kpCiMgICBwcmVkcyA9IHJiaW5kKHByZWRzLHRlX3ByZWRzKQojIH0KIyBkaWFnKGNvcihwcmVkcyxyZWFsKSkKCiMgVXNpbmcgUExTIHJlZ3Jlc3Npb24KIyBsaWJyYXJ5KHBscykKIyBwbHNfbW9kZWwgPSBwbHNyKHl+eCxuY29tcCA9IDUsdmFsaWRhdGlvbj0iTE9PIikKIyBldmFsID0gTVNFUChwbHNfbW9kZWwpCiMgCiMgeV9wY2EgPSBwcmNvbXAoeSkKIyBwbG90KHlfcGNhKQojIGV4cGxhaW5lZF92YXIgPSB5X3BjYSRzZGV2XjIvc3VtKHlfcGNhJHNkZXZeMikKIyB5X3BjYV9tYXRyaXggPSB5X3BjYSR4WywxOjEwXQojIAojICMgcmVncmVzcyBvdXQgc2V4LCB3ZWlnaHQKIyAKIyBnZXRfZXhwbGFpbmVkX3ZhcmlhbmNlX3VzaW5nX1BDQSh4LHkpCiMgeCA9IGFwcGx5KHgsMixyZWdyZXNzX291dCxjb3ZzPWNvdnMpCiMgeSA9IGFwcGx5KHksMixyZWdyZXNzX291dCxjb3ZzPWNvdnMpCiMgZ2V0X2V4cGxhaW5lZF92YXJpYW5jZV91c2luZ19QQ0EoeCx5KQoKCmBgYAoKPCEtLSAjIyBDb21wYXJpc29uIG9mIGNvdmFyaWFuY2UgbWF0cmljZXMgLS0+Cgo8IS0tIGBgYHtyfSAtLT4KCjwhLS0gQ1YgPC1mdW5jdGlvbih4KXsgLS0+CjwhLS0gICByZXR1cm4oc2QoeCkvbWVhbih4KSkgLS0+CjwhLS0gfSAtLT4KCjwhLS0gIyBHZXQgYWxsIGNvcnJlbGF0aW9uIGFuZCBjb3ZhcmlhbmNlIG1hdHJpY2VzIC0tPgo8IS0tIHkgPSBtZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzW1sxXV0kc2FtcGxlX2RhdGEgIyBhbmNob3IgYWxsIGRhdGFzZXRzIGJ5IHRoZXNlIGlkcyAtLT4KPCEtLSB5X3ZpYWxzID0gY29sbmFtZXMoeSkgLS0+CjwhLS0gYmlkX3kgPSBhcy5jaGFyYWN0ZXIobWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeSksImJpZCJdKSAtLT4KPCEtLSBjb3ZfY29scyA9IGMoImFuaW1hbC5yZWdpc3RyYXRpb24uc2V4IiwgLS0+CjwhLS0gICAgICAgICAgICAgICJhY3V0ZS50ZXN0LndlaWdodCIsIC0tPgo8IS0tICAgICAgICAgICAgICAiYWN1dGUudGVzdC5kaXN0YW5jZSIpIC0tPgo8IS0tIGNvdnMgPSBtZXJnZWRfZG1hcWNfZGF0YVt5X3ZpYWxzLGNvdl9jb2xzXSAtLT4KCjwhLS0gY292X21hdHJpY2VzID0gbGlzdCgpIC0tPgo8IS0tIGNvcl9tYXRyaWNlcyA9IGxpc3QoKSAtLT4KPCEtLSBmb3Iobm4gaW4gbmFtZXMobWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0cykpeyAtLT4KPCEtLSAgIHggPSBtZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzW1tubl1dJHNhbXBsZV9kYXRhIC0tPgo8IS0tICAgYmlkX3ggPSBhcy5jaGFyYWN0ZXIobWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeCksImJpZCJdKSAtLT4KPCEtLSAgIHByaW50KHN1bSghaXMuZWxlbWVudChiaWRfeSxzZXQ9YmlkX3gpKSkgIyBzaG91bGQgYmUgemVybyAtLT4KPCEtLSAgIGNvbG5hbWVzKHgpID0gYmlkX3ggLS0+CjwhLS0gICB4ID0geFssYmlkX3ldIC0tPgo8IS0tICAgIyB4ID0gYXBwbHkoeCwxLHJlZ3Jlc3Nfb3V0LGNvdnM9Y292cykgLS0+CjwhLS0gICAjIHggPSB0KHgpIC0tPgo8IS0tICAgIyBjdnMgPSBhcHBseSh4LDEsQ1YpIC0tPgo8IS0tICAgIyBwcmludCh0YWJsZShjdnM+MC41KSkgLS0+CjwhLS0gICAjIHggPSB4W2N2cz4wLjUsXSAtLT4KPCEtLSAgICMgcGNheCA9IHQocHJjb21wKHQoeCksc2NhbGUuID0gRikkeFssMToxMF0pIC0tPgo8IS0tICAgY292X21hdHJpY2VzW1tubl1dID0gY292KHgpIC0tPgo8IS0tICAgY29yX21hdHJpY2VzW1tubl1dID0gY29yKHgpIC0tPgo8IS0tIH0gLS0+CjwhLS0gc2FwcGx5KGNvcl9tYXRyaWNlcyxkaW0pIC0tPgo8IS0tIGxpYnJhcnkoJ2V2b2xxZycpO2xpYnJhcnkoY29ycnBsb3QpIC0tPgo8IS0tIG1hbnRlbF90ZXN0cz0gTWFudGVsQ29yKGNvcl9tYXRyaWNlcykgLS0+CjwhLS0gbWNvcnM9IE1hdHJpeENvcihjb3JfbWF0cmljZXMpIC0tPgo8IS0tIG1jb3JzID0gYXMubWF0cml4KGZvcmNlU3ltbWV0cmljKG1jb3JzLHVwbG89IkwiKSkgLS0+CjwhLS0gbWFudGVsX3Rlc3RzID0gYXMubWF0cml4KCAtLT4KPCEtLSAgIGZvcmNlU3ltbWV0cmljKG1hbnRlbF90ZXN0cyRwcm9iYWJpbGl0aWVzLHVwbG8gPSAiTCIpKSAtLT4KPCEtLSAjIGRpYWcobWFudGVsX3Rlc3RzKT0wIC0tPgo8IS0tIG9yZCA9IGNvcnJwbG90KHQobWNvcnMpLG9yZGVyPSJoY2x1c3QiKSAtLT4KPCEtLSBvcmQgPSByb3duYW1lcyhvcmQpIC0tPgo8IS0tIGNvcnJwbG90KG1jb3JzW29yZCxvcmRdLHAubWF0PW1hbnRlbF90ZXN0c1tvcmQsb3JkXSwgLS0+CjwhLS0gICAgICAgICAgaW5zaWcgPSAibGFiZWxfc2lnIixtZXRob2Q9InNoYWRlIiwgLS0+CjwhLS0gICAgICAgICAgc2lnLmxldmVsID0gYyguMDAwMSwgLjAwMSwgLjAxKSwgLS0+CjwhLS0gICAgICAgICAgcGNoLmNleCA9IC45LCBwY2guY29sID0gImJsYWNrIikgLS0+CjwhLS0gIyByc19yZXMgPSBSYW5kb21Ta2V3ZXJzKGNvcl9tYXRyaWNlcykgLS0+CjwhLS0gIyBjb3JycGxvdChyc19yZXMkY29ycmVsYXRpb25zLCAtLT4KPCEtLSAjICAgICAgICAgIHAubWF0PW1hbnRlbF90ZXN0cyRwcm9iYWJpbGl0aWVzLHR5cGU9Imxvd2VyIiwgLS0+CjwhLS0gIyAgICAgICAgICBpbnNpZyA9ICJsYWJlbF9zaWciLG1ldGhvZD0ic2hhZGUiLCAtLT4KPCEtLSAjICAgICAgICAgIHNpZy5sZXZlbCA9IGMoLjAwMDEsIC4wMDEsIC4wMSksIC0tPgo8IS0tICMgICAgICAgICAgcGNoLmNleCA9IC45LCBwY2guY29sID0gImJsYWNrIix0bC5jZXg9MC42KSAtLT4KCjwhLS0gbGlicmFyeShwc3ljaCkgLS0+Cgo8IS0tIHIxID0gY29yX21hdHJpY2VzW1sxXV0gLS0+CjwhLS0gcjIgPSBjb3JfbWF0cmljZXNbWzhdXSAtLT4KPCEtLSBjb3IocjFbbG93ZXIudHJpKHIxKV0scjJbbG93ZXIudHJpKHIyKV0pIC0tPgo8IS0tIG1hbnRlbF90ZXN0c1sxLDhdIC0tPgo8IS0tIG1hbnRlbF90ZXN0c1s4LDFdIC0tPgoKPCEtLSB0aHJlc2hvbGRfY29tcCA9IGFwcGx5X2Z1bmN0aW9uX29uX3BhaXJzKGNvcl9tYXRyaWNlcywgLS0+CjwhLS0gICAgICAgICAgIGZ1bmN0aW9uKHgseSlzdW0oeD4wLjcmeT4wLjcpKSAtLT4KPCEtLSBjb3JycGxvdCh0aHJlc2hvbGRfY29tcCxpcy5jb3JyID0gRixvcmRlcj0iaGNsdXN0IikgLS0+Cgo8IS0tIHRocmVzaG9sZF9jb21wID0gYXBwbHlfZnVuY3Rpb25fb25fcGFpcnMoY29yX21hdHJpY2VzLCAtLT4KPCEtLSAgICAgICAgICBmdW5jdGlvbih4LHkpbWVhbihkaWFnKGNvcih4LHkpKSkpIC0tPgo8IS0tIGNvcnJwbG90KHRocmVzaG9sZF9jb21wLGlzLmNvcnIgPSBGLG9yZGVyPSJoY2x1c3QiKSAtLT4KCjwhLS0gYGBgIC0tPgoKCgoKCgoKCgoKCgoKCg==